diff --git a/.github/workflows/flake8-linting.yml b/.github/workflows/flake8-linting.yml index 8c1d676a5d4..217f47bd5e9 100644 --- a/.github/workflows/flake8-linting.yml +++ b/.github/workflows/flake8-linting.yml @@ -1,4 +1,4 @@ -name: Check PEP8 compliance with flake8 +name: Check PEP8 compliance with flake8 and autoformat with black on: [push, pull_request] @@ -15,3 +15,8 @@ jobs: python-version: "3.10" - name: flake8 Lint uses: py-actions/flake8@v2 + - name: black autoformat check + uses: psf/black@stable + with: + options: "--check --diff" + version: "~= 23.0" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 987d1548184..8f25c4d91e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,9 @@ repos: - - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v2.0.4 + - repo: https://github.com/psf/black + rev: 23.7.0 hooks: - - id: autopep8 + - id: black - repo: https://github.com/pycqa/flake8 rev: 6.1.0 hooks: - - id: flake8 + - id: flake8 diff --git a/app.py b/app.py index 6f0fed545e3..49dc908ee81 100644 --- a/app.py +++ b/app.py @@ -17,9 +17,20 @@ import static_babel_content from markupsafe import Markup -from flask import (Flask, Response, abort, after_this_request, g, - redirect, request, send_file, url_for, jsonify, - send_from_directory, session) +from flask import ( + Flask, + Response, + abort, + after_this_request, + g, + redirect, + request, + send_file, + url_for, + jsonify, + send_from_directory, + session, +) from flask_babel import Babel, gettext from flask_commonmark import Commonmark from flask_compress import Compress @@ -32,18 +43,56 @@ import utils from safe_format import safe_format from config import config -from website.flask_helpers import render_template, proper_tojson, JinjaCompatibleJsonProvider -from hedy_content import (ADVENTURE_ORDER_PER_LEVEL, KEYWORDS_ADVENTURES, ALL_KEYWORD_LANGUAGES, - ALL_LANGUAGES, COUNTRIES) +from website.flask_helpers import ( + render_template, + proper_tojson, + JinjaCompatibleJsonProvider, +) +from hedy_content import ( + ADVENTURE_ORDER_PER_LEVEL, + KEYWORDS_ADVENTURES, + ALL_KEYWORD_LANGUAGES, + ALL_LANGUAGES, + COUNTRIES, +) from logging_config import LOGGING_CONFIG -from utils import dump_yaml_rt, is_debug_mode, load_yaml_rt, timems, version, strip_accents -from website import (ab_proxying, achievements, admin, auth_pages, aws_helpers, - cdn, classes, database, for_teachers, s3_logger, parsons, - profile, programs, querylog, quiz, statistics, - translating) -from website.auth import (current_user, is_admin, is_teacher, - login_user_from_token_cookie, requires_login, requires_login_redirect, requires_teacher) +from utils import ( + dump_yaml_rt, + is_debug_mode, + load_yaml_rt, + timems, + version, + strip_accents, +) +from website import ( + ab_proxying, + achievements, + admin, + auth_pages, + aws_helpers, + cdn, + classes, + database, + for_teachers, + s3_logger, + parsons, + profile, + programs, + querylog, + quiz, + statistics, + translating, +) +from website.auth import ( + current_user, + is_admin, + is_teacher, + login_user_from_token_cookie, + requires_login, + requires_login_redirect, + requires_teacher, +) from website.log_fetcher import log_fetcher from website.frontend_types import Adventure, Program, ExtraStory, SaveInfo @@ -52,23 +101,30 @@ # Todo TB: This can introduce a possible app breaking bug when switching # to Python 4 -> e.g. Python 4.0.1 is invalid -if (sys.version_info.major < 3 or sys.version_info.minor < 7): - print('Hedy requires Python 3.7 or newer to run. However, your version of Python is', '.'.join( - [str(sys.version_info.major), str(sys.version_info.minor), str(sys.version_info.micro)])) +if sys.version_info.major < 3 or sys.version_info.minor < 7: + print( + "Hedy requires Python 3.7 or newer to run. However, your version of Python is", + ".".join( + [ + str(sys.version_info.major), + str(sys.version_info.minor), + str(sys.version_info.micro), + ] + ), + ) quit() # Set the current directory to the root Hedy folder -os.chdir(os.path.join(os.getcwd(), __file__.replace( - os.path.basename(__file__), ''))) +os.chdir(os.path.join(os.getcwd(), __file__.replace(os.path.basename(__file__), ""))) # Setting up Flask and babel (web and translations) -app = Flask(__name__, static_url_path='') +app = Flask(__name__, static_url_path="") app.url_map.strict_slashes = False # Ignore trailing slashes in URLs app.json = JinjaCompatibleJsonProvider(app) babel = Babel(app) jinja_partials.register_extensions(app) -app.template_filter('tojson')(proper_tojson) +app.template_filter("tojson")(proper_tojson) COMMANDS = collections.defaultdict(hedy_content.NoSuchCommand) for lang in ALL_LANGUAGES.keys(): @@ -115,7 +171,7 @@ def load_adventures_for_level(level): adventures = ADVENTURES[g.lang].get_adventures(keyword_lang) for short_name, adventure in adventures.items(): - adventure_level = adventure['levels'].get(level, None) + adventure_level = adventure["levels"].get(level, None) if not adventure_level: continue @@ -123,53 +179,61 @@ def load_adventures_for_level(level): extra_stories = [ ExtraStory( - text=adventure_level.get(f'story_text_{i}'), - example_code=adventure_level.get(f'example_code_{i}')) + text=adventure_level.get(f"story_text_{i}"), + example_code=adventure_level.get(f"example_code_{i}"), + ) for i in range(2, 10) - if adventure_level.get(f'story_text_{i}', '') + if adventure_level.get(f"story_text_{i}", "") ] - default_save_name = adventure.get('default_save_name') - if not default_save_name or default_save_name == 'intro': - default_save_name = adventure['name'] + default_save_name = adventure.get("default_save_name") + if not default_save_name or default_save_name == "intro": + default_save_name = adventure["name"] # only add adventures that have been added to the adventure list of this level if short_name in ADVENTURE_ORDER_PER_LEVEL.get(level, []): current_adventure = Adventure( short_name=short_name, - name=adventure['name'], - image=adventure.get('image', None), - text=adventure['levels'][level].get('story_text', ""), - example_code=adventure['levels'][level].get('example_code', ""), + name=adventure["name"], + image=adventure.get("image", None), + text=adventure["levels"][level].get("story_text", ""), + example_code=adventure["levels"][level].get("example_code", ""), extra_stories=extra_stories, is_teacher_adventure=False, is_command_adventure=short_name in KEYWORDS_ADVENTURES, - save_name=f'{default_save_name} {level}', - start_code=adventure['levels'][level].get('start_code', "")) + save_name=f"{default_save_name} {level}", + start_code=adventure["levels"][level].get("start_code", ""), + ) all_adventures.append(current_adventure) # Sort the adventures based on the default ordering adventures_order = ADVENTURE_ORDER_PER_LEVEL.get(level, []) index_map = {v: i for i, v in enumerate(adventures_order)} - all_adventures.sort(key=lambda pair: index_map.get( - pair['short_name'], - len(adventures_order))) + all_adventures.sort( + key=lambda pair: index_map.get(pair["short_name"], len(adventures_order)) + ) return all_adventures -def load_saved_programs(level, into_adventures, preferential_program: Optional[Program]): +def load_saved_programs( + level, into_adventures, preferential_program: Optional[Program] +): """Load saved previous saved programs by the current user into the given adventures array. Mutates the adventures in-place, by setting the 'save_name', 'start_code' and 'save_info' attributes of adventures. """ - if not current_user()['username']: + if not current_user()["username"]: return - loaded_programs = {k: Program.from_database_row(r) - for k, r in DATABASE.last_level_programs_for_user(current_user()['username'], level).items()} + loaded_programs = { + k: Program.from_database_row(r) + for k, r in DATABASE.last_level_programs_for_user( + current_user()["username"], level + ).items() + } # If there is a preferential program, overwrite any other one that might exist so we definitely # load this one. @@ -210,37 +274,42 @@ def load_customized_adventures(level, customizations, into_adventures): # { level -> [ { name, from_teacher ] } # 'name' is either a shortname or an ID into the teacher table - sorted_adventures = customizations.get('sorted_adventures', {}) + sorted_adventures = customizations.get("sorted_adventures", {}) order_for_this_level = sorted_adventures.get(str(level), []) if not order_for_this_level: return # Nothing to do - adventure_ids = {a['name'] for a in order_for_this_level if a['from_teacher']} + adventure_ids = {a["name"] for a in order_for_this_level if a["from_teacher"]} teacher_adventure_map = DATABASE.batch_get_adventures(adventure_ids) builtin_adventure_map = {a.short_name: a for a in into_adventures} # Replace `into_adventures` into_adventures[:] = [] for a in order_for_this_level: - if a['from_teacher'] and (db_row := teacher_adventure_map.get(a['name'])): + if a["from_teacher"] and (db_row := teacher_adventure_map.get(a["name"])): try: - db_row['content'] = safe_format(db_row['content'], - **hedy_content.KEYWORDS.get(g.keyword_lang)) + db_row["content"] = safe_format( + db_row["content"], **hedy_content.KEYWORDS.get(g.keyword_lang) + ) except Exception: # We don't want teacher being able to break the student UI -> pass this adventure pass - into_adventures.append(Adventure.from_teacher_adventure_database_row(db_row)) - if not a['from_teacher'] and (adv := builtin_adventure_map.get(a['name'])): + into_adventures.append( + Adventure.from_teacher_adventure_database_row(db_row) + ) + if not a["from_teacher"] and (adv := builtin_adventure_map.get(a["name"])): into_adventures.append(adv) @babel.localeselector def get_locale(): - return session.get("lang", request.accept_languages.best_match(ALL_LANGUAGES.keys(), 'en')) + return session.get( + "lang", request.accept_languages.best_match(ALL_LANGUAGES.keys(), "en") + ) -cdn.Cdn(app, os.getenv('CDN_PREFIX'), os.getenv('HEROKU_SLUG_COMMIT', 'dev')) +cdn.Cdn(app, os.getenv("CDN_PREFIX"), os.getenv("HEROKU_SLUG_COMMIT", "dev")) @app.before_request @@ -250,13 +319,17 @@ def before_request_begin_logging(): This needs to happen as one of the first things, as the database calls etc. depend on it. """ - path = (str(request.path) + '?' + request.query_string.decode('utf-8') - ) if request.query_string else str(request.path) + path = ( + (str(request.path) + "?" + request.query_string.decode("utf-8")) + if request.query_string + else str(request.path) + ) querylog.begin_global_log_record( path=path, method=request.method, - remote_ip=request.headers.get('X-Forwarded-For', request.remote_addr), - user_agent=request.headers.get('User-Agent')) + remote_ip=request.headers.get("X-Forwarded-For", request.remote_addr), + user_agent=request.headers.get("User-Agent"), + ) @app.after_request @@ -283,54 +356,60 @@ def initialize_session(): login_user_from_token_cookie() g.user = current_user() - querylog.log_value(session_id=utils.session_id(), username=g.user['username'], - is_teacher=is_teacher(g.user), is_admin=is_admin(g.user)) + querylog.log_value( + session_id=utils.session_id(), + username=g.user["username"], + is_teacher=is_teacher(g.user), + is_admin=is_admin(g.user), + ) + +if os.getenv("IS_PRODUCTION"): -if os.getenv('IS_PRODUCTION'): @app.before_request def reject_e2e_requests(): if utils.is_testing_request(request): - return 'No E2E tests are allowed in production', 400 + return "No E2E tests are allowed in production", 400 @app.before_request def before_request_proxy_testing(): - if utils.is_testing_request(request) and os.getenv('IS_TEST_ENV'): - session['test_session'] = 'test' + if utils.is_testing_request(request) and os.getenv("IS_TEST_ENV"): + session["test_session"] = "test" # HTTP -> HTTPS redirect # https://stackoverflow.com/questions/32237379/python-flask-redirect-to-https-from-http/32238093 -if os.getenv('REDIRECT_HTTP_TO_HTTPS'): +if os.getenv("REDIRECT_HTTP_TO_HTTPS"): + @app.before_request def before_request_https(): - if request.url.startswith('http://'): - url = request.url.replace('http://', 'https://', 1) + if request.url.startswith("http://"): + url = request.url.replace("http://", "https://", 1) # We use a 302 in case we need to revert the redirect. return redirect(url, code=302) + # Unique random key for sessions. # For settings with multiple workers, an environment variable is required, # otherwise cookies will be constantly removed and re-set by different # workers. if utils.is_production(): - if not os.getenv('SECRET_KEY'): - raise RuntimeError( - 'The SECRET KEY must be provided for non-dev environments.') + if not os.getenv("SECRET_KEY"): + raise RuntimeError("The SECRET KEY must be provided for non-dev environments.") - app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') + app.config["SECRET_KEY"] = os.getenv("SECRET_KEY") else: # The value doesn't matter for dev environments, but it needs to be a constant # so that our cookies don't get invalidated every time we restart the server. - app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'WeAreDeveloping') + app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", "WeAreDeveloping") if utils.is_heroku(): app.config.update( SESSION_COOKIE_SECURE=True, SESSION_COOKIE_HTTPONLY=True, - SESSION_COOKIE_SAMESITE='Lax', + SESSION_COOKIE_SAMESITE="Lax", ) # Set security attributes for cookies in a central place - but not when @@ -339,8 +418,7 @@ def before_request_https(): Compress(app) Commonmark(app) parse_logger = s3_logger.S3ParseLogger.from_env_vars() -querylog.LOG_QUEUE.set_transmitter( - aws_helpers.s3_querylog_transmitter_from_env()) +querylog.LOG_QUEUE.set_transmitter(aws_helpers.s3_querylog_transmitter_from_env()) @app.before_request @@ -350,50 +428,59 @@ def setup_language(): # If not in the request parameters, use the browser's accept-languages # header to do language negotiation. Can be changed in the session by # POSTing to `/change_language`, and be overwritten by remember_current_user(). - if lang_from_request := request.args.get('language', None): - session['lang'] = lang_from_request - if 'lang' not in session: - session['lang'] = request.accept_languages.best_match( - ALL_LANGUAGES.keys(), 'en') - g.lang = session['lang'] - - if 'keyword_lang' not in session: - session['keyword_lang'] = g.lang if g.lang in ALL_KEYWORD_LANGUAGES.keys() else 'en' + if lang_from_request := request.args.get("language", None): + session["lang"] = lang_from_request + if "lang" not in session: + session["lang"] = request.accept_languages.best_match( + ALL_LANGUAGES.keys(), "en" + ) + g.lang = session["lang"] + + if "keyword_lang" not in session: + session["keyword_lang"] = ( + g.lang if g.lang in ALL_KEYWORD_LANGUAGES.keys() else "en" + ) # If there is a '?keyword_language=' parameter, it overrides the current keyword lang, permanently - if request.args.get('keyword_language', None): - session['keyword_lang'] = request.args.get('keyword_language', None) - g.keyword_lang = session['keyword_lang'] + if request.args.get("keyword_language", None): + session["keyword_lang"] = request.args.get("keyword_language", None) + g.keyword_lang = session["keyword_lang"] # Set the page direction -> automatically set it to "left-to-right" # Switch to "right-to-left" if one of the language is rtl according to Locale (from Babel) settings. # This is the only place to expand / shrink the list of RTL languages -> # front-end is fixed based on this value - g.dir = static_babel_content.TEXT_DIRECTIONS.get(g.lang, 'ltr') + g.dir = static_babel_content.TEXT_DIRECTIONS.get(g.lang, "ltr") # Check that requested language is supported, otherwise return 404 if g.lang not in ALL_LANGUAGES.keys(): return "Language " + g.lang + " not supported", 404 -if utils.is_heroku() and not os.getenv('HEROKU_RELEASE_CREATED_AT'): +if utils.is_heroku() and not os.getenv("HEROKU_RELEASE_CREATED_AT"): logger.warning( - 'Cannot determine release; enable Dyno metadata by running' - '"heroku labs:enable runtime-dyno-metadata -a "') + "Cannot determine release; enable Dyno metadata by running" + '"heroku labs:enable runtime-dyno-metadata -a "' + ) # A context processor injects variables in the context that are available to all templates. @app.context_processor def enrich_context_with_user_info(): user = current_user() - data = {'username': user.get('username', ''), - 'is_teacher': is_teacher(user), 'is_admin': is_admin(user)} + data = { + "username": user.get("username", ""), + "is_teacher": is_teacher(user), + "is_admin": is_admin(user), + } return data @app.context_processor def add_generated_css_file(): return { - "generated_css_file": '/css/generated.full.css' if is_debug_mode() else '/css/generated.css' + "generated_css_file": "/css/generated.full.css" + if is_debug_mode() + else "/css/generated.css" } @@ -403,10 +490,10 @@ def add_hx_detection(): A template may decide to render things differently when it is vs. when it isn't. """ - hx_request = bool(request.headers.get('Hx-Request')) + hx_request = bool(request.headers.get("Hx-Request")) return { "hx_request": hx_request, - "hx_layout": 'htmx-layout-yes.html' if hx_request else 'htmx-layout-no.html', + "hx_layout": "htmx-layout-yes.html" if hx_request else "htmx-layout-no.html", } @@ -417,20 +504,20 @@ def hx_triggers(response): Use the HX-Trigger header, which will trigger events on the client. There is a listener there which will respond to the 'displayAchievements' event. """ - if not request.headers.get('HX-Request'): + if not request.headers.get("HX-Request"): return response - achs = session.pop('pending_achievements', []) + achs = session.pop("pending_achievements", []) if achs: - response.headers.set('HX-Trigger', json.dumps({'displayAchievements': achs})) + response.headers.set("HX-Trigger", json.dumps({"displayAchievements": achs})) return response @app.after_request def set_security_headers(response): security_headers = { - 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', - 'X-XSS-Protection': '1; mode=block', + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-XSS-Protection": "1; mode=block", } # Not X-Frame-Options on purpose -- we are being embedded by Online Masters # and that's okay. @@ -446,88 +533,98 @@ def teardown_request_finish_logging(exc): # If present, PROXY_TO_TEST_HOST should be the 'http[s]://hostname[:port]' of the target environment -if os.getenv('PROXY_TO_TEST_HOST') and not os.getenv('IS_TEST_ENV'): - ab_proxying.ABProxying(app, os.getenv( - 'PROXY_TO_TEST_HOST'), app.config['SECRET_KEY']) +if os.getenv("PROXY_TO_TEST_HOST") and not os.getenv("IS_TEST_ENV"): + ab_proxying.ABProxying( + app, os.getenv("PROXY_TO_TEST_HOST"), app.config["SECRET_KEY"] + ) -@app.route('/session_test', methods=['GET']) +@app.route("/session_test", methods=["GET"]) def echo_session_vars_test(): if not utils.is_testing_request(request): - return 'This endpoint is only meant for E2E tests', 400 - return jsonify({'session': dict(session)}) + return "This endpoint is only meant for E2E tests", 400 + return jsonify({"session": dict(session)}) -@app.route('/session_main', methods=['GET']) +@app.route("/session_main", methods=["GET"]) def echo_session_vars_main(): if not utils.is_testing_request(request): - return 'This endpoint is only meant for E2E tests', 400 - return jsonify({'session': dict(session), - 'proxy_enabled': bool(os.getenv('PROXY_TO_TEST_HOST'))}) + return "This endpoint is only meant for E2E tests", 400 + return jsonify( + { + "session": dict(session), + "proxy_enabled": bool(os.getenv("PROXY_TO_TEST_HOST")), + } + ) -@app.route('/parse', methods=['POST']) +@app.route("/parse", methods=["POST"]) def parse(): body = request.json if not body: return "body must be an object", 400 - if 'code' not in body: + if "code" not in body: return "body.code must be a string", 400 - if 'level' not in body: + if "level" not in body: return "body.level must be a string", 400 - if 'adventure_name' in body and not isinstance(body['adventure_name'], str): + if "adventure_name" in body and not isinstance(body["adventure_name"], str): return "if present, body.adventure_name must be a string", 400 # TODO: Once we figure out whats wrong with the skip faulty code, we need to reinstantiate this # if 'skip_faulty' not in body: # return "body.skip_faulty must be a boolean", 400 error_check = False - if 'error_check' in body: + if "error_check" in body: error_check = True - code = body['code'] - level = int(body['level']) + code = body["code"] + level = int(body["level"]) skip_faulty = False # bool(body['skip_faulty']) # Language should come principally from the request body, # but we'll fall back to browser default if it's missing for whatever # reason. - lang = body.get('lang', g.lang) + lang = body.get("lang", g.lang) # true if kid enabled the read aloud option - read_aloud = body.get('read_aloud', False) + read_aloud = body.get("read_aloud", False) response = {} - username = current_user()['username'] or None + username = current_user()["username"] or None exception = None - querylog.log_value(level=level, lang=lang, - session_id=utils.session_id(), username=username) + querylog.log_value( + level=level, lang=lang, session_id=utils.session_id(), username=username + ) try: keyword_lang = current_keyword_language()["lang"] - with querylog.log_time('transpile'): + with querylog.log_time("transpile"): try: transpile_result = transpile_add_stats(code, level, lang, skip_faulty) - if username and not body.get('tutorial'): + if username and not body.get("tutorial"): DATABASE.increase_user_run_count(username) ACHIEVEMENTS.increase_count("run") except hedy.exceptions.WarningException as ex: - translated_error = translate_error(ex.error_code, ex.arguments, keyword_lang) + translated_error = translate_error( + ex.error_code, ex.arguments, keyword_lang + ) if isinstance(ex, hedy.exceptions.InvalidSpaceException): - response['Warning'] = translated_error + response["Warning"] = translated_error else: - response['Error'] = translated_error - response['Location'] = ex.error_location + response["Error"] = translated_error + response["Location"] = ex.error_location transpile_result = ex.fixed_result exception = ex except hedy.exceptions.UnquotedEqualityCheck as ex: - response['Error'] = translate_error(ex.error_code, ex.arguments, keyword_lang) - response['Location'] = ex.error_location + response["Error"] = translate_error( + ex.error_code, ex.arguments, keyword_lang + ) + response["Location"] = ex.error_location exception = ex try: - response['Code'] = transpile_result.code + response["Code"] = transpile_result.code # source_map_result = transpile_result.source_map.get_result() # for i, mapping in source_map_result.items(): @@ -539,23 +636,28 @@ def parse(): # ) # response['source_map'] = source_map_result - response['source_map'] = transpile_result.source_map.get_result() + response["source_map"] = transpile_result.source_map.get_result() if transpile_result.has_pygame: - response['has_pygame'] = True + response["has_pygame"] = True if transpile_result.has_turtle: - response['has_turtle'] = True + response["has_turtle"] = True except Exception: pass try: - response['has_sleep'] = 'sleep' in hedy.all_commands(code, level, lang) + response["has_sleep"] = "sleep" in hedy.all_commands(code, level, lang) except BaseException: pass try: - if username and not body.get('tutorial') and ACHIEVEMENTS.verify_run_achievements( - username, code, level, response): - response['achievements'] = ACHIEVEMENTS.get_earned_achievements() + if ( + username + and not body.get("tutorial") + and ACHIEVEMENTS.verify_run_achievements( + username, code, level, response + ) + ): + response["achievements"] = ACHIEVEMENTS.get_earned_achievements() except Exception as E: print(f"error determining achievements for {code} with {E}") @@ -571,100 +673,107 @@ def parse(): exception = E # Save this program (if the user is logged in) - if username and body.get('save_name'): + if username and body.get("save_name"): try: program_logic = programs.ProgramsLogic(DATABASE, ACHIEVEMENTS) program = program_logic.store_user_program( user=current_user(), level=level, - name=body.get('save_name'), - program_id=body.get('program_id'), - adventure_name=body.get('adventure_name'), + name=body.get("save_name"), + program_id=body.get("program_id"), + adventure_name=body.get("adventure_name"), code=code, - error=exception is not None) + error=exception is not None, + ) - response['save_info'] = SaveInfo.from_program(Program.from_database_row(program)) + response["save_info"] = SaveInfo.from_program( + Program.from_database_row(program) + ) except programs.NotYourProgramError: # No permissions to overwrite, no biggie pass - querylog.log_value(server_error=response.get('Error')) - parse_logger.log({ - 'session': utils.session_id(), - 'date': str(datetime.datetime.now()), - 'level': level, - 'lang': lang, - 'code': code, - 'server_error': response.get('Error'), - 'exception': get_class_name(exception), - 'version': version(), - 'username': username, - 'read_aloud': read_aloud, - 'is_test': 1 if os.getenv('IS_TEST_ENV') else None, - 'adventure_name': body.get('adventure_name', None) - }) + querylog.log_value(server_error=response.get("Error")) + parse_logger.log( + { + "session": utils.session_id(), + "date": str(datetime.datetime.now()), + "level": level, + "lang": lang, + "code": code, + "server_error": response.get("Error"), + "exception": get_class_name(exception), + "version": version(), + "username": username, + "read_aloud": read_aloud, + "is_test": 1 if os.getenv("IS_TEST_ENV") else None, + "adventure_name": body.get("adventure_name", None), + } + ) if "Error" in response and error_check: - response["message"] = gettext('program_contains_error') + response["message"] = gettext("program_contains_error") return jsonify(response) -@app.route('/parse-by-id', methods=['POST']) +@app.route("/parse-by-id", methods=["POST"]) @requires_login def parse_by_id(user): body = request.json # Validations if not isinstance(body, dict): - return 'body must be an object', 400 - if not isinstance(body.get('id'), str): - return 'class id must be a string', 400 + return "body must be an object", 400 + if not isinstance(body.get("id"), str): + return "class id must be a string", 400 - program = DATABASE.program_by_id(body.get('id')) - if program and program.get('username') == user['username']: + program = DATABASE.program_by_id(body.get("id")) + if program and program.get("username") == user["username"]: try: hedy.transpile( - program.get('code'), - program.get('level'), - program.get('lang') + program.get("code"), program.get("level"), program.get("lang") ) return {}, 200 except BaseException: return {"error": "parsing error"}, 200 else: - return 'this is not your program!', 400 + return "this is not your program!", 400 -@app.route('/parse_tutorial', methods=['POST']) +@app.route("/parse_tutorial", methods=["POST"]) @requires_login def parse_tutorial(user): body = request.json - code = body['code'] - level = try_parse_int(body['level']) + code = body["code"] + level = try_parse_int(body["level"]) try: result = hedy.transpile(code, level, "en") - jsonify({'code': result.code}), 200 + jsonify({"code": result.code}), 200 except BaseException: return "error", 400 -@app.route("/generate_machine_files", methods=['POST']) +@app.route("/generate_machine_files", methods=["POST"]) def prepare_files(): body = request.json # Prepare the file -> return the "secret" filename as response - transpiled_code = hedy.transpile(body.get("code"), body.get("level"), body.get("lang")) + transpiled_code = hedy.transpile( + body.get("code"), body.get("level"), body.get("lang") + ) filename = utils.random_id_generator(12) # We have to turn the turtle 90 degrees to align with the user perspective app.ts#16 # This is not a really nice solution, but as we store the prefix on the # front-end it should be good for now - threader = textwrap.dedent(""" + threader = textwrap.dedent( + """ import time from turtlethread import Turtle t = Turtle() t.left(90) with t.running_stitch(stitch_length=20): - """) + """ + ) lines = transpiled_code.code.split("\n") # remove all sleeps for speeed, and remove all colors for compatibility: @@ -673,23 +782,27 @@ def prepare_files(): threader += " " + "\n ".join(lines) threader += "\n" + 't.save("machine_files/' + filename + '.dst")' threader += "\n" + 't.save("machine_files/' + filename + '.png")' - if not os.path.isdir('machine_files'): - os.makedirs('machine_files') + if not os.path.isdir("machine_files"): + os.makedirs("machine_files") exec(threader) # stolen from: https://stackoverflow.com/questions/28568687/send-with-multiple-csvs-using-flask - zip_file = zipfile.ZipFile(f'machine_files/{filename}.zip', 'w', zipfile.ZIP_DEFLATED) - for root, dirs, files in os.walk('machine_files/'): + zip_file = zipfile.ZipFile( + f"machine_files/{filename}.zip", "w", zipfile.ZIP_DEFLATED + ) + for root, dirs, files in os.walk("machine_files/"): # only zip files for this request, and exclude the zip file itself: - for file in [x for x in files if x[:len(filename)] == filename and x[-3:] != 'zip']: - zip_file.write('machine_files/' + file) + for file in [ + x for x in files if x[: len(filename)] == filename and x[-3:] != "zip" + ]: + zip_file.write("machine_files/" + file) zip_file.close() - return jsonify({'filename': filename}), 200 + return jsonify({"filename": filename}), 200 -@app.route("/download_machine_files/", methods=['GET']) +@app.route("/download_machine_files/", methods=["GET"]) def download_machine_file(filename, extension="zip"): # https://stackoverflow.com/questions/24612366/delete-an-uploaded-file-after-downloading-it-from-flask @@ -708,17 +821,23 @@ def remove_file(response): def transpile_add_stats(code, level, lang_, skip_faulty): - username = current_user()['username'] or None - number_of_lines = code.count('\n') + username = current_user()["username"] or None + number_of_lines = code.count("\n") try: result = hedy.transpile(code, level, lang_, skip_faulty) statistics.add( - username, lambda id_: DATABASE.add_program_stats(id_, level, number_of_lines, None)) + username, + lambda id_: DATABASE.add_program_stats(id_, level, number_of_lines, None), + ) return result except Exception as ex: class_name = get_class_name(ex) - statistics.add(username, lambda id_: DATABASE.add_program_stats( - id_, level, number_of_lines, class_name)) + statistics.add( + username, + lambda id_: DATABASE.add_program_stats( + id_, level, number_of_lines, class_name + ), + ) raise @@ -732,49 +851,53 @@ def hedy_error_to_response(ex): keyword_lang = current_keyword_language()["lang"] return { "Error": translate_error(ex.error_code, ex.arguments, keyword_lang), - "Location": ex.error_location + "Location": ex.error_location, } def translate_error(code, arguments, keyword_lang): arguments_that_require_translation = [ - 'allowed_types', - 'invalid_type', - 'invalid_type_2', - 'character_found', - 'concept', - 'tip', - 'else', - 'command', - 'print', - 'ask', - 'echo', - 'is', - 'if', - 'repeat'] + "allowed_types", + "invalid_type", + "invalid_type_2", + "character_found", + "concept", + "tip", + "else", + "command", + "print", + "ask", + "echo", + "is", + "if", + "repeat", + ] arguments_that_require_highlighting = [ - 'command', - 'guessed_command', - 'invalid_argument', - 'invalid_argument_2', - 'variable', - 'invalid_value', - 'print', - 'else', - 'ask', - 'echo', - 'is', - 'if', - 'repeat'] + "command", + "guessed_command", + "invalid_argument", + "invalid_argument_2", + "variable", + "invalid_value", + "print", + "else", + "ask", + "echo", + "is", + "if", + "repeat", + ] # Todo TB -> We have to find a more delicate way to fix this: returns some gettext() errors - error_template = gettext('' + str(code)) + error_template = gettext("" + str(code)) # Fetch tip if it exists and merge into template, since it can also contain placeholders # that need to be translated/highlighted - if 'tip' in arguments: - error_template = error_template.replace("{tip}", gettext('' + str(arguments['tip']))) + if "tip" in arguments: + error_template = error_template.replace( + "{tip}", gettext("" + str(arguments["tip"])) + ) # TODO, FH Oct 2022 -> Could we do this with a format even though we don't have all fields? # adds keywords to the dictionary so they can be translated if they occur in the error text @@ -795,11 +918,13 @@ def translate_error(code, arguments, keyword_lang): if isinstance(v, list): arguments[k] = translate_list(v) else: - arguments[k] = gettext('' + str(v)) + arguments[k] = gettext("" + str(v)) if k in arguments_that_require_highlighting: if k in arguments_that_require_translation: - local_keyword = hedy_translation.translate_keyword_from_en(v, keyword_lang) + local_keyword = hedy_translation.translate_keyword_from_en( + v, keyword_lang + ) arguments[k] = hedy.style_command(local_keyword) else: arguments[k] = hedy.style_command(v) @@ -808,37 +933,41 @@ def translate_error(code, arguments, keyword_lang): def translate_list(args): - translated_args = [gettext('' + str(a)) for a in args] + translated_args = [gettext("" + str(a)) for a in args] # Deduplication is needed because diff values could be translated to the # same value, e.g. int and float => a number translated_args = list(dict.fromkeys(translated_args)) if len(translated_args) > 1: - return f"{', '.join(translated_args[0:-1])}" \ - f" {gettext('or')} " \ - f"{translated_args[-1]}" - return ''.join(translated_args) + return ( + f"{', '.join(translated_args[0:-1])}" + f" {gettext('or')} " + f"{translated_args[-1]}" + ) + return "".join(translated_args) -@app.route('/report_error', methods=['POST']) +@app.route("/report_error", methods=["POST"]) def report_error(): post_body = request.json - parse_logger.log({ - 'session': utils.session_id(), - 'date': str(datetime.datetime.now()), - 'level': post_body.get('level'), - 'code': post_body.get('code'), - 'client_error': post_body.get('client_error'), - 'version': version(), - 'username': current_user()['username'] or None, - 'is_test': 1 if os.getenv('IS_TEST_ENV') else None - }) + parse_logger.log( + { + "session": utils.session_id(), + "date": str(datetime.datetime.now()), + "level": post_body.get("level"), + "code": post_body.get("code"), + "client_error": post_body.get("client_error"), + "version": version(), + "username": current_user()["username"] or None, + "is_test": 1 if os.getenv("IS_TEST_ENV") else None, + } + ) - return 'logged' + return "logged" -@app.route('/client_exception', methods=['POST']) +@app.route("/client_exception", methods=["POST"]) def report_client_exception(): post_body = request.json @@ -847,186 +976,198 @@ def report_client_exception(): date=str(datetime.datetime.now()), client_error=post_body, version=version(), - username=current_user()['username'] or None, - is_test=1 if os.getenv('IS_TEST_ENV') else None + username=current_user()["username"] or None, + is_test=1 if os.getenv("IS_TEST_ENV") else None, ) # Return a 500 so the HTTP status codes will stand out in our monitoring/logging - return 'logged', 500 + return "logged", 500 -@app.route('/version', methods=['GET']) +@app.route("/version", methods=["GET"]) def version_page(): """ - Generate a page with some diagnostic information and a useful GitHub URL on upcoming changes. + Generate a page with some diagnostic information and a useful GitHub URL on upcoming changes. - This is an admin-only page, it does not need to be linked. - (Also does not have any sensitive information so it's fine to be unauthenticated). + This is an admin-only page, it does not need to be linked. + (Also does not have any sensitive information so it's fine to be unauthenticated). """ - app_name = os.getenv('HEROKU_APP_NAME') + app_name = os.getenv("HEROKU_APP_NAME") - vrz = os.getenv('HEROKU_RELEASE_CREATED_AT') - the_date = datetime.date.fromisoformat( - vrz[:10]) if vrz else datetime.date.today() + vrz = os.getenv("HEROKU_RELEASE_CREATED_AT") + the_date = datetime.date.fromisoformat(vrz[:10]) if vrz else datetime.date.today() - commit = os.getenv('HEROKU_SLUG_COMMIT', '????')[0:6] + commit = os.getenv("HEROKU_SLUG_COMMIT", "????")[0:6] - return render_template('version-page.html', - app_name=app_name, - heroku_release_time=the_date, - commit=commit) + return render_template( + "version-page.html", + app_name=app_name, + heroku_release_time=the_date, + commit=commit, + ) -@app.route('/commands/') +@app.route("/commands/") def all_commands(id): program = DATABASE.program_by_id(id) - code = program.get('code') - level = program.get('level') - lang = program.get('lang') + code = program.get("code") + level = program.get("level") + lang = program.get("lang") return render_template( - 'commands.html', - commands=hedy.all_commands(code, level, lang)) + "commands.html", commands=hedy.all_commands(code, level, lang) + ) -@app.route('/my-achievements') +@app.route("/my-achievements") def achievements_page(): user = current_user() - username = user['username'] + username = user["username"] if not username: # redirect users to /login if they are not logged in # Todo: TB -> I wrote this once, but wouldn't it make more sense to simply # throw a 302 error? - url = request.url.replace('/my-achievements', '/login') + url = request.url.replace("/my-achievements", "/login") return redirect(url, code=302) - user_achievements = DATABASE.achievements_by_username(user.get('username')) or [] - achievements = ACHIEVEMENTS_TRANSLATIONS.get_translations(g.lang).get('achievements') + user_achievements = DATABASE.achievements_by_username(user.get("username")) or [] + achievements = ACHIEVEMENTS_TRANSLATIONS.get_translations(g.lang).get( + "achievements" + ) return render_template( - 'achievements.html', - page_title=gettext('title_achievements'), + "achievements.html", + page_title=gettext("title_achievements"), translations=achievements, user_achievements=user_achievements, - current_page='my-profile') + current_page="my-profile", + ) -@app.route('/programs', methods=['GET']) +@app.route("/programs", methods=["GET"]) @requires_login_redirect def programs_page(user): - username = user['username'] + username = user["username"] if not username: # redirect users to /login if they are not logged in - url = request.url.replace('/programs', '/login') + url = request.url.replace("/programs", "/login") return redirect(url, code=302) - from_user = request.args.get('user') or None + from_user = request.args.get("user") or None # If from_user -> A teacher is trying to view the user programs if from_user and not is_admin(user): if not is_teacher(user): - return utils.error_page(error=403, ui_message=gettext('not_teacher')) + return utils.error_page(error=403, ui_message=gettext("not_teacher")) students = DATABASE.get_teacher_students(username) if from_user not in students: - return utils.error_page(error=403, ui_message=gettext('not_enrolled')) + return utils.error_page(error=403, ui_message=gettext("not_enrolled")) # We request our own page -> also get the public_profile settings public_profile = None if not from_user: public_profile = DATABASE.get_public_profile_settings(username) - level = request.args.get('level', default=None, type=str) or None - adventure = request.args.get('adventure', default=None, type=str) or None - page = request.args.get('page', default=None, type=str) - filter = request.args.get('filter', default=None, type=str) - submitted = True if filter == 'submitted' else None - - result = DATABASE.filtered_programs_for_user(from_user or username, - level=level, - adventure=adventure, - submitted=submitted, - pagination_token=page, - limit=10) + level = request.args.get("level", default=None, type=str) or None + adventure = request.args.get("adventure", default=None, type=str) or None + page = request.args.get("page", default=None, type=str) + filter = request.args.get("filter", default=None, type=str) + submitted = True if filter == "submitted" else None + + result = DATABASE.filtered_programs_for_user( + from_user or username, + level=level, + adventure=adventure, + submitted=submitted, + pagination_token=page, + limit=10, + ) programs = [] for item in result: - date = utils.delta_timestamp(item['date']) + date = utils.delta_timestamp(item["date"]) # This way we only keep the first 4 lines to show as preview to the user - code = "\n".join(item['code'].split("\n")[:4]) + code = "\n".join(item["code"].split("\n")[:4]) programs.append( - {'id': item['id'], - 'code': code, - 'date': date, - 'level': item['level'], - 'name': item['name'], - 'adventure_name': item.get('adventure_name'), - 'submitted': item.get('submitted'), - 'public': item.get('public'), - 'number_lines': item['code'].count('\n') + 1 - } + { + "id": item["id"], + "code": code, + "date": date, + "level": item["level"], + "name": item["name"], + "adventure_name": item.get("adventure_name"), + "submitted": item.get("submitted"), + "public": item.get("public"), + "number_lines": item["code"].count("\n") + 1, + } ) adventure_names = hedy_content.Adventures(g.lang).get_adventure_names() - next_page_url = url_for('programs_page', **dict(request.args, page=result.next_page_token) - ) if result.next_page_token else None + next_page_url = ( + url_for("programs_page", **dict(request.args, page=result.next_page_token)) + if result.next_page_token + else None + ) return render_template( - 'programs.html', + "programs.html", programs=programs, - page_title=gettext('title_programs'), - current_page='programs', + page_title=gettext("title_programs"), + current_page="programs", from_user=from_user, public_profile=public_profile, adventure_names=adventure_names, max_level=hedy.HEDY_MAX_LEVEL, - next_page_url=next_page_url) + next_page_url=next_page_url, + ) -@app.route('/logs/query', methods=['POST']) +@app.route("/logs/query", methods=["POST"]) def query_logs(): user = current_user() if not is_admin(user) and not is_teacher(user): - return utils.error_page(error=403, ui_message=gettext('unauthorized')) + return utils.error_page(error=403, ui_message=gettext("unauthorized")) body = request.json if body is not None and not isinstance(body, dict): - return 'body must be an object', 400 + return "body must be an object", 400 - class_id = body.get('class_id') + class_id = body.get("class_id") if not is_admin(user): - username_filter = body.get('username') + username_filter = body.get("username") if not class_id or not username_filter: - return utils.error_page(error=403, ui_message=gettext('unauthorized')) + return utils.error_page(error=403, ui_message=gettext("unauthorized")) class_ = DATABASE.get_class(class_id) - if not class_ or class_['teacher'] != user['username'] or username_filter not in class_.get('students', [ - ]): - return utils.error_page(error=403, ui_message=gettext('unauthorized')) + if ( + not class_ + or class_["teacher"] != user["username"] + or username_filter not in class_.get("students", []) + ): + return utils.error_page(error=403, ui_message=gettext("unauthorized")) (exec_id, status) = log_fetcher.query(body) - response = {'query_status': status, 'query_execution_id': exec_id} + response = {"query_status": status, "query_execution_id": exec_id} return jsonify(response) -@app.route('/logs/results', methods=['GET']) +@app.route("/logs/results", methods=["GET"]) def get_log_results(): - query_execution_id = request.args.get( - 'query_execution_id', default=None, type=str) - next_token = request.args.get('next_token', default=None, type=str) + query_execution_id = request.args.get("query_execution_id", default=None, type=str) + next_token = request.args.get("next_token", default=None, type=str) user = current_user() if not is_admin(user) and not is_teacher(user): - return utils.error_page(error=403, ui_message=gettext('unauthorized')) + return utils.error_page(error=403, ui_message=gettext("unauthorized")) - data, next_token = log_fetcher.get_query_results( - query_execution_id, next_token) - response = {'data': data, 'next_token': next_token} + data, next_token = log_fetcher.get_query_results(query_execution_id, next_token) + response = {"data": data, "next_token": next_token} return jsonify(response) -@app.route('/tutorial', methods=['GET']) +@app.route("/tutorial", methods=["GET"]) def tutorial_index(): - if not current_user()['username']: - return redirect('/login') + if not current_user()["username"]: + return redirect("/login") level = 1 cheatsheet = COMMANDS[g.lang].get_commands_for_level(level, g.keyword_lang) commands = hedy.commands_per_level.get(level) @@ -1050,64 +1191,71 @@ def tutorial_index(): initial_adventure=initial_adventure, cheatsheet=cheatsheet, blur_button_available=False, - current_user_is_in_class=len(current_user().get('classes') or []) > 0, + current_user_is_in_class=len(current_user().get("classes") or []) > 0, # See initialize.ts javascript_page_options=dict( - page='code', + page="code", level=level, lang=g.lang, adventures=adventures, initial_tab=initial_tab, - current_user_name=current_user()['username'], + current_user_name=current_user()["username"], start_tutorial=True, - )) + ), + ) -@app.route('/teacher-tutorial', methods=['GET']) +@app.route("/teacher-tutorial", methods=["GET"]) @requires_teacher def teacher_tutorial(user): - teacher_classes = DATABASE.get_teacher_classes(user['username'], True) + teacher_classes = DATABASE.get_teacher_classes(user["username"], True) adventures = [] - for adventure in DATABASE.get_teacher_adventures(user['username']): + for adventure in DATABASE.get_teacher_adventures(user["username"]): adventures.append( - {'id': adventure.get('id'), - 'name': adventure.get('name'), - 'date': utils.localized_date_format(adventure.get('date')), - 'level': adventure.get('level') - } + { + "id": adventure.get("id"), + "name": adventure.get("name"), + "date": utils.localized_date_format(adventure.get("date")), + "level": adventure.get("level"), + } ) - return render_template('for-teachers.html', current_page='my-profile', - page_title=gettext('title_for-teacher'), - teacher_classes=teacher_classes, - teacher_adventures=adventures, - tutorial=True, - content=hedyweb.PageTranslations('for-teachers').get_page_translations(g.lang), - javascript_page_options=dict( - page='for-teachers', - tutorial=True, - )) + return render_template( + "for-teachers.html", + current_page="my-profile", + page_title=gettext("title_for-teacher"), + teacher_classes=teacher_classes, + teacher_adventures=adventures, + tutorial=True, + content=hedyweb.PageTranslations("for-teachers").get_page_translations(g.lang), + javascript_page_options=dict( + page="for-teachers", + tutorial=True, + ), + ) # routing to index.html -@app.route('/ontrack', methods=['GET'], defaults={'level': '1', 'program_id': None}) -@app.route('/onlinemasters', methods=['GET'], defaults={'level': '1', 'program_id': None}) -@app.route('/onlinemasters/', methods=['GET'], defaults={'program_id': None}) -@app.route('/hedy/', methods=['GET'], defaults={'program_id': None}) -@app.route('/hedy//', methods=['GET']) +@app.route("/ontrack", methods=["GET"], defaults={"level": "1", "program_id": None}) +@app.route( + "/onlinemasters", methods=["GET"], defaults={"level": "1", "program_id": None} +) +@app.route("/onlinemasters/", methods=["GET"], defaults={"program_id": None}) +@app.route("/hedy/", methods=["GET"], defaults={"program_id": None}) +@app.route("/hedy//", methods=["GET"]) def index(level, program_id): try: level = int(level) if level < 1 or level > hedy.HEDY_MAX_LEVEL: - return utils.error_page(error=404, ui_message=gettext('no_such_level')) + return utils.error_page(error=404, ui_message=gettext("no_such_level")) except BaseException: - return utils.error_page(error=404, ui_message=gettext('no_such_level')) + return utils.error_page(error=404, ui_message=gettext("no_such_level")) loaded_program = None if program_id: result = DATABASE.program_by_id(program_id) if not result or not current_user_allowed_to_see_program(result): - return utils.error_page(error=404, ui_message=gettext('no_such_program')) + return utils.error_page(error=404, ui_message=gettext("no_such_program")) loaded_program = Program.from_database_row(result) @@ -1118,59 +1266,74 @@ def index(level, program_id): available_levels = list(range(1, hedy.HEDY_MAX_LEVEL + 1)) customizations = {} - if current_user()['username']: - customizations = DATABASE.get_student_class_customizations(current_user()['username']) + if current_user()["username"]: + customizations = DATABASE.get_student_class_customizations( + current_user()["username"] + ) - if 'levels' in customizations: - available_levels = customizations['levels'] + if "levels" in customizations: + available_levels = customizations["levels"] now = timems() - for current_level, timestamp in customizations.get('opening_dates', {}).items(): - if utils.datetotimeordate(timestamp) > utils.datetotimeordate(utils.mstoisostring(now)): + for current_level, timestamp in customizations.get("opening_dates", {}).items(): + if utils.datetotimeordate(timestamp) > utils.datetotimeordate( + utils.mstoisostring(now) + ): try: available_levels.remove(int(current_level)) except BaseException: print("Error: there is an openings date without a level") - if 'levels' in customizations and level not in available_levels: - return utils.error_page(error=403, ui_message=gettext('level_not_class')) + if "levels" in customizations and level not in available_levels: + return utils.error_page(error=403, ui_message=gettext("level_not_class")) # At this point we can have the following scenario: # - The level is allowed and available # - But, if there is a quiz threshold we have to check again if the user has reached it - if 'level_thresholds' in customizations: - if 'quiz' in customizations.get('level_thresholds'): + if "level_thresholds" in customizations: + if "quiz" in customizations.get("level_thresholds"): # Temporary store the threshold - threshold = customizations.get('level_thresholds').get('quiz') + threshold = customizations.get("level_thresholds").get("quiz") # Get the max quiz score of the user in the previous level # A bit out-of-scope, but we want to enable the next level button directly after finishing the quiz # Todo: How can we fix this without a re-load? - quiz_stats = DATABASE.get_quiz_stats([current_user()['username']]) + quiz_stats = DATABASE.get_quiz_stats([current_user()["username"]]) # Only check the quiz threshold if there is a quiz to obtain a score on the previous level if level > 1 and QUIZZES[g.lang].get_quiz_data_for_level(level - 1): - scores = [x.get('scores', []) for x in quiz_stats if x.get('level') == level - 1] + scores = [ + x.get("scores", []) + for x in quiz_stats + if x.get("level") == level - 1 + ] scores = [score for week_scores in scores for score in week_scores] max_score = 0 if len(scores) < 1 else max(scores) if max_score < threshold: return utils.error_page( - error=403, ui_message=gettext('quiz_threshold_not_reached')) + error=403, ui_message=gettext("quiz_threshold_not_reached") + ) # We also have to check if the next level should be removed from the available_levels # Only check the quiz threshold if there is a quiz to obtain a score on the current level - if level < hedy.HEDY_MAX_LEVEL and QUIZZES[g.lang].get_quiz_data_for_level(level): - scores = [x.get('scores', []) for x in quiz_stats if x.get('level') == level] + if level < hedy.HEDY_MAX_LEVEL and QUIZZES[g.lang].get_quiz_data_for_level( + level + ): + scores = [ + x.get("scores", []) for x in quiz_stats if x.get("level") == level + ] scores = [score for week_scores in scores for score in week_scores] max_score = 0 if len(scores) < 1 else max(scores) # We don't have the score yet for the next level -> remove all upcoming # levels from 'available_levels' if max_score < threshold: # if this level is currently available, but score is below max score - customizations["below_threshold"] = (level + 1 in available_levels) - available_levels = available_levels[:available_levels.index(level) + 1] + customizations["below_threshold"] = level + 1 in available_levels + available_levels = available_levels[ + : available_levels.index(level) + 1 + ] # Add the available levels to the customizations dict -> simplify # implementation on the front-end - customizations['available_levels'] = available_levels + customizations["available_levels"] = available_levels cheatsheet = COMMANDS[g.lang].get_commands_for_level(level, g.keyword_lang) load_customized_adventures(level, customizations, adventures) @@ -1181,26 +1344,35 @@ def index(level, program_id): # Make sure that there is an adventure(/tab) for a loaded program, otherwise make one initial_tab = loaded_program.adventure_name if not any(a.short_name == loaded_program.adventure_name for a in adventures): - adventures.append(Adventure( - short_name=loaded_program.adventure_name, - # This is not great but we got nothing better :) - name=gettext('your_program'), - text='', - example_code='', - start_code=loaded_program.code, - save_name=loaded_program.name, - is_teacher_adventure=False, - is_command_adventure=loaded_program.adventure_name in KEYWORDS_ADVENTURES - )) + adventures.append( + Adventure( + short_name=loaded_program.adventure_name, + # This is not great but we got nothing better :) + name=gettext("your_program"), + text="", + example_code="", + start_code=loaded_program.code, + save_name=loaded_program.name, + is_teacher_adventure=False, + is_command_adventure=loaded_program.adventure_name + in KEYWORDS_ADVENTURES, + ) + ) adventures_map = {a.short_name: a for a in adventures} enforce_developers_mode = False - if 'other_settings' in customizations and 'developers_mode' in customizations['other_settings']: + if ( + "other_settings" in customizations + and "developers_mode" in customizations["other_settings"] + ): enforce_developers_mode = True hide_cheatsheet = False - if 'other_settings' in customizations and 'hide_cheatsheet' in customizations['other_settings']: + if ( + "other_settings" in customizations + and "hide_cheatsheet" in customizations["other_settings"] + ): hide_cheatsheet = True parsons = True if PARSONS[g.lang].get_parsons_data_for_level(level) else False @@ -1215,9 +1387,15 @@ def index(level, program_id): if parsons: parson_exercises = len(PARSONS[g.lang].get_parsons_data_for_level(level)) - if 'other_settings' in customizations and 'hide_parsons' in customizations['other_settings']: + if ( + "other_settings" in customizations + and "hide_parsons" in customizations["other_settings"] + ): parsons = False - if 'other_settings' in customizations and 'hide_quiz' in customizations['other_settings']: + if ( + "other_settings" in customizations + and "hide_quiz" in customizations["other_settings"] + ): quiz = False max_level = hedy.HEDY_MAX_LEVEL @@ -1228,7 +1406,7 @@ def index(level, program_id): "code-page.html", level_nr=str(level_number), level=level_number, - current_page='hedy', + current_page="hedy", prev_level=level_number - 1 if level_number > 1 else None, next_level=level_number + 1 if level_number < max_level else None, customizations=customizations, @@ -1247,24 +1425,25 @@ def index(level, program_id): cheatsheet=cheatsheet, blur_button_available=False, initial_adventure=adventures_map[initial_tab], - current_user_is_in_class=len(current_user().get('classes') or []) > 0, + current_user_is_in_class=len(current_user().get("classes") or []) > 0, # See initialize.ts javascript_page_options=dict( - page='code', + page="code", level=level_number, lang=g.lang, adventures=adventures, initial_tab=initial_tab, - current_user_name=current_user()['username'], - )) + current_user_name=current_user()["username"], + ), + ) -@app.route('/hedy', methods=['GET']) +@app.route("/hedy", methods=["GET"]) def index_level(): - if current_user()['username']: - highest_quiz = get_highest_quiz_level(current_user()['username']) + if current_user()["username"]: + highest_quiz = get_highest_quiz_level(current_user()["username"]) # This function returns the character '-' in case there are no finished quizes - if highest_quiz == '-': + if highest_quiz == "-": level_rendered = 1 elif highest_quiz == hedy.HEDY_MAX_LEVEL: level_rendered = hedy.HEDY_MAX_LEVEL @@ -1275,22 +1454,30 @@ def index_level(): return index(1, None) -@app.route('/hedy//view', methods=['GET']) +@app.route("/hedy//view", methods=["GET"]) @requires_login def view_program(user, id): result = DATABASE.program_by_id(id) if not result or not current_user_allowed_to_see_program(result): - return utils.error_page(error=404, ui_message=gettext('no_such_program')) + return utils.error_page(error=404, ui_message=gettext("no_such_program")) # The program is valid, verify if the creator also have a public profile - result['public_profile'] = True if DATABASE.get_public_profile_settings( - result['username']) else None + result["public_profile"] = ( + True if DATABASE.get_public_profile_settings(result["username"]) else None + ) - code = result['code'] - if result.get("lang") != "en" and result.get("lang") in ALL_KEYWORD_LANGUAGES.keys(): - code = hedy_translation.translate_keywords(code, from_lang=result.get( - 'lang'), to_lang="en", level=int(result.get('level', 1))) + code = result["code"] + if ( + result.get("lang") != "en" + and result.get("lang") in ALL_KEYWORD_LANGUAGES.keys() + ): + code = hedy_translation.translate_keywords( + code, + from_lang=result.get("lang"), + to_lang="en", + level=int(result.get("level", 1)), + ) # If the keyword language is non-English -> parse again to guarantee # completely localized keywords if g.keyword_lang != "en": @@ -1298,124 +1485,127 @@ def view_program(user, id): code, from_lang="en", to_lang=g.keyword_lang, - level=int( - result.get( - 'level', - 1))) + level=int(result.get("level", 1)), + ) - result['code'] = code + result["code"] = code arguments_dict = {} - arguments_dict['program_id'] = id - arguments_dict['page_title'] = f'{result["name"]} – Hedy' - arguments_dict['level'] = result['level'] # Necessary for running - arguments_dict['initial_adventure'] = dict(result, - start_code=code, - ) - arguments_dict['editor_readonly'] = True - - if "submitted" in result and result['submitted']: - arguments_dict['show_edit_button'] = False - arguments_dict['program_timestamp'] = utils.localized_date_format(result['date']) + arguments_dict["program_id"] = id + arguments_dict["page_title"] = f'{result["name"]} – Hedy' + arguments_dict["level"] = result["level"] # Necessary for running + arguments_dict["initial_adventure"] = dict( + result, + start_code=code, + ) + arguments_dict["editor_readonly"] = True + + if "submitted" in result and result["submitted"]: + arguments_dict["show_edit_button"] = False + arguments_dict["program_timestamp"] = utils.localized_date_format( + result["date"] + ) else: - arguments_dict['show_edit_button'] = True + arguments_dict["show_edit_button"] = True # Everything below this line has nothing to do with this page and it's silly # that every page needs to put in so much effort to re-set it - return render_template("view-program-page.html", - blur_button_available=True, - javascript_page_options=dict( - page='view-program', - lang=g.lang, - level=int(result['level'])), - **arguments_dict) + return render_template( + "view-program-page.html", + blur_button_available=True, + javascript_page_options=dict( + page="view-program", lang=g.lang, level=int(result["level"]) + ), + **arguments_dict, + ) -@app.route('/adventure/', methods=['GET'], defaults={'level': 1, 'mode': 'full'}) -@app.route('/adventure//', methods=['GET'], defaults={'mode': 'full'}) -@app.route('/adventure///', methods=['GET']) +@app.route("/adventure/", methods=["GET"], defaults={"level": 1, "mode": "full"}) +@app.route("/adventure//", methods=["GET"], defaults={"mode": "full"}) +@app.route("/adventure///", methods=["GET"]) def get_specific_adventure(name, level, mode): try: level = int(level) except BaseException: - return utils.error_page(error=404, ui_message=gettext('no_such_level')) + return utils.error_page(error=404, ui_message=gettext("no_such_level")) adventures = [x for x in load_adventures_for_level(level) if x.short_name == name] if not adventures: - return utils.error_page(error=404, ui_message=gettext('no_such_adventure')) + return utils.error_page(error=404, ui_message=gettext("no_such_adventure")) prev_level = None # we are not rendering buttons in raw, no lookup needed here next_level = None # Add the commands to enable the language switcher dropdown commands = hedy.commands_per_level.get(level) - raw = mode == 'raw' + raw = mode == "raw" initial_tab = name initial_adventure = adventures[0] - return render_template("code-page.html", - specific_adventure=True, - level_nr=str(level), - commands=commands, - level=level, - prev_level=prev_level, - next_level=next_level, - customizations=[], - hide_cheatsheet=None, - enforce_developers_mode=None, - teacher_adventures=[], - adventures=adventures, - initial_tab=initial_tab, - initial_adventure=initial_adventure, - latest=version(), - raw=raw, - menu=not raw, - blur_button_available=False, - current_user_is_in_class=len(current_user().get('classes') or []) > 0, - # See initialize.ts - javascript_page_options=dict( - - page='code', - lang=g.lang, - level=level, - adventures=adventures, - initial_tab=initial_tab, - current_user_name=current_user()['username'], - )) - - -@app.route('/cheatsheet/', methods=['GET'], defaults={'level': 1}) -@app.route('/cheatsheet/', methods=['GET']) + return render_template( + "code-page.html", + specific_adventure=True, + level_nr=str(level), + commands=commands, + level=level, + prev_level=prev_level, + next_level=next_level, + customizations=[], + hide_cheatsheet=None, + enforce_developers_mode=None, + teacher_adventures=[], + adventures=adventures, + initial_tab=initial_tab, + initial_adventure=initial_adventure, + latest=version(), + raw=raw, + menu=not raw, + blur_button_available=False, + current_user_is_in_class=len(current_user().get("classes") or []) > 0, + # See initialize.ts + javascript_page_options=dict( + page="code", + lang=g.lang, + level=level, + adventures=adventures, + initial_tab=initial_tab, + current_user_name=current_user()["username"], + ), + ) + + +@app.route("/cheatsheet/", methods=["GET"], defaults={"level": 1}) +@app.route("/cheatsheet/", methods=["GET"]) def get_cheatsheet_page(level): try: level = int(level) if level < 1 or level > hedy.HEDY_MAX_LEVEL: - return utils.error_page(error=404, ui_message=gettext('no_such_level')) + return utils.error_page(error=404, ui_message=gettext("no_such_level")) except BaseException: - return utils.error_page(error=404, ui_message=gettext('no_such_level')) + return utils.error_page(error=404, ui_message=gettext("no_such_level")) commands = COMMANDS[g.lang].get_commands_for_level(level, g.keyword_lang) return render_template("printable/cheatsheet.html", commands=commands, level=level) -@app.route('/certificate/', methods=['GET']) +@app.route("/certificate/", methods=["GET"]) def get_certificate_page(username): - if not current_user()['username']: - return utils.error_page(error=403, ui_message=gettext('unauthorized')) + if not current_user()["username"]: + return utils.error_page(error=403, ui_message=gettext("unauthorized")) username = username.lower() user = DATABASE.user_by_username(username) if not user: - return utils.error_page(error=403, ui_message=gettext('user_inexistent')) + return utils.error_page(error=403, ui_message=gettext("user_inexistent")) progress_data = DATABASE.progress_by_username(username) if progress_data is None: - return utils.error_page(error=404, ui_message=gettext('no_certificate')) - achievements = progress_data.get('achieved', None) + return utils.error_page(error=404, ui_message=gettext("no_certificate")) + achievements = progress_data.get("achieved", None) if achievements is None: - return utils.error_page(error=404, ui_message=gettext('no_certificate')) - if 'run_programs' in progress_data: - count_programs = progress_data['run_programs'] + return utils.error_page(error=404, ui_message=gettext("no_certificate")) + if "run_programs" in progress_data: + count_programs = progress_data["run_programs"] else: count_programs = 0 quiz_score = get_highest_quiz_score(username) @@ -1423,24 +1613,34 @@ def get_certificate_page(username): longest_program = get_longest_program(username) number_achievements = len(achievements) - congrats_message = safe_format(gettext('congrats_message'), username=username) - return render_template("printable/certificate.html", count_programs=count_programs, quiz_score=quiz_score, - longest_program=longest_program, number_achievements=number_achievements, - quiz_level=quiz_level, congrats_message=congrats_message) + congrats_message = safe_format(gettext("congrats_message"), username=username) + return render_template( + "printable/certificate.html", + count_programs=count_programs, + quiz_score=quiz_score, + longest_program=longest_program, + number_achievements=number_achievements, + quiz_level=quiz_level, + congrats_message=congrats_message, + ) def get_highest_quiz_level(username): quiz_scores = DATABASE.get_quiz_stats([username]) # Verify if the user did finish any quiz before getting the max() of the finished levels finished_quizzes = any("finished" in x for x in quiz_scores) - return max([x.get("level") for x in quiz_scores if x.get("finished")]) if finished_quizzes else "-" + return ( + max([x.get("level") for x in quiz_scores if x.get("finished")]) + if finished_quizzes + else "-" + ) def get_highest_quiz_score(username): max = 0 quizzes = DATABASE.get_quiz_stats([username]) for q in quizzes: - for score in q.get('scores', []): + for score in q.get("scores", []): if score > max: max = score return max @@ -1450,120 +1650,130 @@ def get_longest_program(username): programs = DATABASE.get_program_stats([username]) highest = 0 for program in programs: - if 'number_of_lines' in program: - highest = max(highest, program['number_of_lines']) + if "number_of_lines" in program: + highest = max(highest, program["number_of_lines"]) return highest @app.errorhandler(404) def not_found(exception): - return utils.error_page(error=404, ui_message=gettext('page_not_found')) + return utils.error_page(error=404, ui_message=gettext("page_not_found")) @app.errorhandler(500) def internal_error(exception): import traceback + print(traceback.format_exc()) return utils.error_page(error=500, exception=exception) -@app.route('/signup', methods=['GET']) +@app.route("/signup", methods=["GET"]) def signup_page(): - if current_user()['username']: - return redirect('/my-profile') - return render_template('signup.html', page_title=gettext('title_signup'), current_page='login') + if current_user()["username"]: + return redirect("/my-profile") + return render_template( + "signup.html", page_title=gettext("title_signup"), current_page="login" + ) -@app.route('/login', methods=['GET']) +@app.route("/login", methods=["GET"]) def login_page(): - if current_user()['username']: - return redirect('/my-profile') - return render_template('login.html', page_title=gettext('title_login'), current_page='login') + if current_user()["username"]: + return redirect("/my-profile") + return render_template( + "login.html", page_title=gettext("title_login"), current_page="login" + ) -@app.route('/recover', methods=['GET']) +@app.route("/recover", methods=["GET"]) def recover_page(): - if current_user()['username']: - return redirect('/my-profile') + if current_user()["username"]: + return redirect("/my-profile") return render_template( - 'recover.html', - page_title=gettext('title_recover'), - current_page='login') + "recover.html", page_title=gettext("title_recover"), current_page="login" + ) -@app.route('/reset', methods=['GET']) +@app.route("/reset", methods=["GET"]) def reset_page(): # If there is a user logged in -> don't allow password reset - if current_user()['username']: - return redirect('/my-profile') + if current_user()["username"]: + return redirect("/my-profile") - username = request.args.get('username', default=None, type=str) - token = request.args.get('token', default=None, type=str) + username = request.args.get("username", default=None, type=str) + token = request.args.get("token", default=None, type=str) username = None if username == "null" else username token = None if token == "null" else token if not username or not token: - return utils.error_page(error=403, ui_message=gettext('unauthorized')) + return utils.error_page(error=403, ui_message=gettext("unauthorized")) return render_template( - 'reset.html', - page_title=gettext('title_reset'), + "reset.html", + page_title=gettext("title_reset"), reset_username=username, reset_token=token, - current_page='login') + current_page="login", + ) -@app.route('/my-profile', methods=['GET']) +@app.route("/my-profile", methods=["GET"]) @requires_login_redirect def profile_page(user): - profile = DATABASE.user_by_username(user['username']) - programs = DATABASE.public_programs_for_user(user['username']) - public_profile_settings = DATABASE.get_public_profile_settings(current_user()['username']) + profile = DATABASE.user_by_username(user["username"]) + programs = DATABASE.public_programs_for_user(user["username"]) + public_profile_settings = DATABASE.get_public_profile_settings( + current_user()["username"] + ) classes = [] - if profile.get('classes'): - for class_id in profile.get('classes'): + if profile.get("classes"): + for class_id in profile.get("classes"): classes.append(DATABASE.get_class(class_id)) - invite = DATABASE.get_username_invite(user['username']) + invite = DATABASE.get_username_invite(user["username"]) if invite: # We have to keep this in mind as well, can simply be set to 1 for now # But when adding more message structures we have to use a more sophisticated structure - session['messages'] = 1 + session["messages"] = 1 # If there is an invite: retrieve the class information - class_info = DATABASE.get_class(invite.get('class_id', None)) + class_info = DATABASE.get_class(invite.get("class_id", None)) if class_info: - invite['teacher'] = class_info.get('teacher') - invite['class_name'] = class_info.get('name') - invite['join_link'] = class_info.get('link') + invite["teacher"] = class_info.get("teacher") + invite["class_name"] = class_info.get("name") + invite["join_link"] = class_info.get("link") else: - session['messages'] = 0 + session["messages"] = 0 return render_template( - 'profile.html', - page_title=gettext('title_my-profile'), + "profile.html", + page_title=gettext("title_my-profile"), programs=programs, user_data=profile, invite_data=invite, public_settings=public_profile_settings, user_classes=classes, - current_page='my-profile') + current_page="my-profile", + ) -@app.route('/research/', methods=['GET']) +@app.route("/research/", methods=["GET"]) def get_research(filename): - return send_from_directory('content/research/', filename) + return send_from_directory("content/research/", filename) -@app.route('/favicon.ico') +@app.route("/favicon.ico") def favicon(): abort(404) -@app.route('/') -@app.route('/start') -@app.route('/index.html') +@app.route("/") +@app.route("/start") +@app.route("/index.html") def main_page(): - sections = hedyweb.PageTranslations('start').get_page_translations(g.lang)['start-sections'] + sections = hedyweb.PageTranslations("start").get_page_translations(g.lang)[ + "start-sections" + ] sections = sections[:] @@ -1572,65 +1782,85 @@ def main_page(): # Styles are one of: 'block', 'pane-with-image-{left,right}', 'columns' # Do this by mutating the list in place content = [] - content.append(dict(style='block', **sections.pop(0))) + content.append(dict(style="block", **sections.pop(0))) section_images = [ - '/images/hedy-multilang.png', - '/images/hedy-grows.png', - '/images/hedy-classroom.png' + "/images/hedy-multilang.png", + "/images/hedy-grows.png", + "/images/hedy-classroom.png", ] for i, image in enumerate(section_images): if not sections: break - content.append(dict( - style='pane-with-image-' + ('right' if i % 2 == 0 else 'left'), - image=image, - **sections.pop(0))) + content.append( + dict( + style="pane-with-image-" + ("right" if i % 2 == 0 else "left"), + image=image, + **sections.pop(0), + ) + ) if sections: - content.append(dict(style='block', **sections.pop(0))) + content.append(dict(style="block", **sections.pop(0))) if sections: - content.append(dict(style='columns', columns=sections)) + content.append(dict(style="columns", columns=sections)) custom_logo = False - if os.path.isfile(f'static/images/hero-graphic/hero-graphic-{g.lang}.png'): + if os.path.isfile(f"static/images/hero-graphic/hero-graphic-{g.lang}.png"): custom_logo = True - return render_template('main-page.html', page_title=gettext('title_start'), custom_logo=custom_logo, - current_page='start', content=content) + return render_template( + "main-page.html", + page_title=gettext("title_start"), + custom_logo=custom_logo, + current_page="start", + content=content, + ) -@app.route('/learn-more') +@app.route("/learn-more") def learn_more(): - learn_more_translations = hedyweb.PageTranslations('learn-more').get_page_translations(g.lang) + learn_more_translations = hedyweb.PageTranslations( + "learn-more" + ).get_page_translations(g.lang) return render_template( - 'learn-more.html', + "learn-more.html", papers=hedy_content.RESEARCH, - page_title=gettext('title_learn-more'), - current_page='learn-more', - content=learn_more_translations) + page_title=gettext("title_learn-more"), + current_page="learn-more", + content=learn_more_translations, + ) -@app.route('/join') +@app.route("/join") def join(): - join_translations = hedyweb.PageTranslations('join').get_page_translations(g.lang) - return render_template('join.html', page_title=gettext('title_learn-more'), - current_page='join', content=join_translations) + join_translations = hedyweb.PageTranslations("join").get_page_translations(g.lang) + return render_template( + "join.html", + page_title=gettext("title_learn-more"), + current_page="join", + content=join_translations, + ) -@app.route('/privacy') +@app.route("/privacy") def privacy(): - privacy_translations = hedyweb.PageTranslations('privacy').get_page_translations(g.lang) - return render_template('privacy.html', page_title=gettext('title_privacy'), - content=privacy_translations) + privacy_translations = hedyweb.PageTranslations("privacy").get_page_translations( + g.lang + ) + return render_template( + "privacy.html", + page_title=gettext("title_privacy"), + content=privacy_translations, + ) -@app.route('/landing-page/', methods=['GET'], defaults={'first': False}) -@app.route('/landing-page/', methods=['GET']) +@app.route("/landing-page/", methods=["GET"], defaults={"first": False}) +@app.route("/landing-page/", methods=["GET"]) @requires_login def landing_page(user, first): - username = user['username'] + username = user["username"] user_info = DATABASE.get_public_profile_settings(username) user_programs = DATABASE.programs_for_user(username) @@ -1640,40 +1870,45 @@ def landing_page(user, first): user_achievements = DATABASE.progress_by_username(username) return render_template( - 'landing-page.html', + "landing-page.html", first_time=True if first else False, - page_title=gettext('title_landing-page'), - user=user['username'], + page_title=gettext("title_landing-page"), + user=user["username"], user_info=user_info, program=user_programs, - achievements=user_achievements) + achievements=user_achievements, + ) -@app.route('/explore', methods=['GET']) +@app.route("/explore", methods=["GET"]) def explore(): - if not current_user()['username']: - return redirect('/login') + if not current_user()["username"]: + return redirect("/login") - level = try_parse_int(request.args.get('level', default=None, type=str)) - adventure = request.args.get('adventure', default=None, type=str) + level = try_parse_int(request.args.get("level", default=None, type=str)) + adventure = request.args.get("adventure", default=None, type=str) language = g.lang achievement = None if level or adventure or language: achievement = ACHIEVEMENTS.add_single_achievement( - current_user()['username'], "indiana_jones") + current_user()["username"], "indiana_jones" + ) - programs = normalize_public_programs(DATABASE.get_public_programs( - limit=40, - level_filter=level, - language_filter=language, - adventure_filter=adventure)) + programs = normalize_public_programs( + DATABASE.get_public_programs( + limit=40, + level_filter=level, + language_filter=language, + adventure_filter=adventure, + ) + ) favourite_programs = normalize_public_programs(DATABASE.get_hedy_choices()) - adventures_names = hedy_content.Adventures(session['lang']).get_adventure_names() + adventures_names = hedy_content.Adventures(session["lang"]).get_adventure_names() return render_template( - 'explore.html', + "explore.html", programs=programs, favourite_programs=favourite_programs, filtered_level=str(level) if level else None, @@ -1682,8 +1917,9 @@ def explore(): filtered_lang=language, max_level=hedy.HEDY_MAX_LEVEL, adventures_names=adventures_names, - page_title=gettext('title_explore'), - current_page='explore') + page_title=gettext("title_explore"), + current_page="explore", + ) def normalize_public_programs(programs): @@ -1702,10 +1938,14 @@ def normalize_public_programs(programs): for program in programs: program = pre_process_explore_program(program) - ret.append(dict(program, - hedy_choice=True if program.get('hedy_choice') == 1 else False, - code="\n".join(program['code'].split("\n")[:4]), - number_lines=program['code'].count('\n') + 1)) + ret.append( + dict( + program, + hedy_choice=True if program.get("hedy_choice") == 1 else False, + code="\n".join(program["code"].split("\n")[:4]), + number_lines=program["code"].count("\n") + 1, + ) + ) DATABASE.add_public_profile_information(ret) return ret @@ -1713,77 +1953,88 @@ def normalize_public_programs(programs): @querylog.timed def pre_process_explore_program(program): # If program does not have an error value set -> parse it and set value - if 'error' not in program: + if "error" not in program: try: - hedy.transpile(program.get('code'), program.get('level'), program.get('lang')) - program['error'] = False + hedy.transpile( + program.get("code"), program.get("level"), program.get("lang") + ) + program["error"] = False except BaseException: - program['error'] = True + program["error"] = True DATABASE.store_program(program) return program -@app.route('/highscores', methods=['GET'], defaults={'filter': 'global'}) -@app.route('/highscores/', methods=['GET']) +@app.route("/highscores", methods=["GET"], defaults={"filter": "global"}) +@app.route("/highscores/", methods=["GET"]) @requires_login def get_highscores_page(user, filter): if filter not in ["global", "country", "class"]: - return utils.error_page(error=404, ui_message=gettext('page_not_found')) + return utils.error_page(error=404, ui_message=gettext("page_not_found")) - user_data = DATABASE.user_by_username(user['username']) - public_profile = True if DATABASE.get_public_profile_settings(user['username']) else False - classes = list(user_data.get('classes', set())) - country = user_data.get('country') + user_data = DATABASE.user_by_username(user["username"]) + public_profile = ( + True if DATABASE.get_public_profile_settings(user["username"]) else False + ) + classes = list(user_data.get("classes", set())) + country = user_data.get("country") user_country = COUNTRIES.get(country) if filter == "global": - highscores = DATABASE.get_highscores(user['username'], filter) + highscores = DATABASE.get_highscores(user["username"], filter) elif filter == "country": # Can't get a country highscore if you're not in a country! if not country: - return utils.error_page(error=403, ui_message=gettext('no_such_highscore')) - highscores = DATABASE.get_highscores(user['username'], filter, country) + return utils.error_page(error=403, ui_message=gettext("no_such_highscore")) + highscores = DATABASE.get_highscores(user["username"], filter, country) elif filter == "class": # Can't get a class highscore if you're not in a class! if not classes: - return utils.error_page(error=403, ui_message=gettext('no_such_highscore')) - highscores = DATABASE.get_highscores(user['username'], filter, classes[0]) + return utils.error_page(error=403, ui_message=gettext("no_such_highscore")) + highscores = DATABASE.get_highscores(user["username"], filter, classes[0]) # Make a deepcopy if working locally, otherwise the local database values # are by-reference and overwritten - if not os.getenv('NO_DEBUG_MODE'): + if not os.getenv("NO_DEBUG_MODE"): highscores = copy.deepcopy(highscores) for highscore in highscores: - highscore['country'] = highscore.get('country') if highscore.get('country') else "-" - highscore['last_achievement'] = utils.delta_timestamp(highscore.get('last_achievement')) + highscore["country"] = ( + highscore.get("country") if highscore.get("country") else "-" + ) + highscore["last_achievement"] = utils.delta_timestamp( + highscore.get("last_achievement") + ) return render_template( - 'highscores.html', + "highscores.html", highscores=highscores, has_country=True if country else False, filter=filter, user_country=user_country, public_profile=public_profile, - in_class=True if classes else False) + in_class=True if classes else False, + ) -@app.route('/change_language', methods=['POST']) +@app.route("/change_language", methods=["POST"]) def change_language(): body = request.json - session['lang'] = body.get('lang') + session["lang"] = body.get("lang") # Remove 'keyword_lang' from session, it will automatically be renegotiated from 'lang' # on the next page load. - session.pop('keyword_lang') - return jsonify({'succes': 200}) + session.pop("keyword_lang") + return jsonify({"succes": 200}) -@app.route('/slides', methods=['GET'], defaults={'level': '1'}) -@app.route('/slides/', methods=['GET']) +@app.route("/slides", methods=["GET"], defaults={"level": "1"}) +@app.route("/slides/", methods=["GET"]) def get_slides(level): # In case of a "forced keyword language" -> load that one, otherwise: load # the one stored in the g object - keyword_language = request.args.get('keyword_language', default=g.keyword_lang, type=str) + keyword_language = request.args.get( + "keyword_language", default=g.keyword_lang, type=str + ) try: level = int(level) @@ -1794,63 +2045,71 @@ def get_slides(level): return utils.error_page(error=404, ui_message="Slides do not exist!") slides = SLIDES[g.lang].get_slides_for_level(level, keyword_language) - return render_template('slides.html', slides=slides) + return render_template("slides.html", slides=slides) -@app.route('/translate_keywords', methods=['POST']) +@app.route("/translate_keywords", methods=["POST"]) def translate_keywords(): body = request.json try: - translated_code = hedy_translation.translate_keywords(body.get('code'), body.get( - 'start_lang'), body.get('goal_lang'), level=int(body.get('level', 1))) + translated_code = hedy_translation.translate_keywords( + body.get("code"), + body.get("start_lang"), + body.get("goal_lang"), + level=int(body.get("level", 1)), + ) if translated_code: - return jsonify({'success': 200, 'code': translated_code}) + return jsonify({"success": 200, "code": translated_code}) else: - return gettext('translate_error'), 400 + return gettext("translate_error"), 400 except BaseException: - return gettext('translate_error'), 400 + return gettext("translate_error"), 400 # TODO TB: Think about changing this to sending all steps to the front-end at once -@app.route('/get_tutorial_step//', methods=['GET']) +@app.route("/get_tutorial_step//", methods=["GET"]) def get_tutorial_translation(level, step): # Keep this structure temporary until we decide on a nice code / parse structure if step == "code_snippet": - code = hedy_content.deep_translate_keywords(gettext('tutorial_code_snippet'), g.keyword_lang) - return jsonify({'code': code}), 200 + code = hedy_content.deep_translate_keywords( + gettext("tutorial_code_snippet"), g.keyword_lang + ) + return jsonify({"code": code}), 200 try: step = int(step) except ValueError: - return gettext('invalid_tutorial_step'), 400 + return gettext("invalid_tutorial_step"), 400 data = TUTORIALS[g.lang].get_tutorial_for_level_step(level, step, g.keyword_lang) if not data: - data = {'title': gettext('tutorial_title_not_found'), - 'text': gettext('tutorial_message_not_found')} + data = { + "title": gettext("tutorial_title_not_found"), + "text": gettext("tutorial_message_not_found"), + } return jsonify(data), 200 -@app.route('/store_parsons_order', methods=['POST']) +@app.route("/store_parsons_order", methods=["POST"]) def store_parsons_order(): body = request.json # Validations if not isinstance(body, dict): - return 'body must be an object', 400 - if not isinstance(body.get('level'), int): - return 'level must be an integer', 400 - if not isinstance(body.get('exercise'), str): - return 'exercise must be a string', 400 - if not isinstance(body.get('order'), list): - return 'order must be a list', 400 + return "body must be an object", 400 + if not isinstance(body.get("level"), int): + return "level must be an integer", 400 + if not isinstance(body.get("exercise"), str): + return "exercise must be a string", 400 + if not isinstance(body.get("order"), list): + return "order must be a list", 400 attempt = { - 'id': utils.random_id_generator(12), - 'username': current_user()['username'] or f'anonymous:{utils.session_id()}', - 'level': int(body['level']), - 'exercise': int(body['exercise']), - 'order': body['order'], - 'correct': 1 if body['correct'] else 0, - 'timestamp': utils.timems() + "id": utils.random_id_generator(12), + "username": current_user()["username"] or f"anonymous:{utils.session_id()}", + "level": int(body["level"]), + "exercise": int(body["exercise"]), + "order": body["order"], + "correct": 1 if body["correct"] else 0, + "timestamp": utils.timems(), } DATABASE.store_parsons(attempt) @@ -1871,7 +2130,7 @@ def current_keyword_language(): def other_keyword_language(): # If the current keyword language isn't English: we are sure the other option is English # But, only if the user has an existing keyword language -> on the session - if session.get('keyword_lang') and session['keyword_lang'] != "en": + if session.get("keyword_lang") and session["keyword_lang"] != "en": return make_keyword_lang_obj("en") return None @@ -1890,7 +2149,7 @@ def markdown_retain_newlines(x): # # Nobody is going to type this voluntarily to distinguish between linebreaks line by # line, but you can use this filter to do this for all line breaks. - return x.replace('\n', ' \n') + return x.replace("\n", " \n") @app.template_filter() @@ -1903,10 +2162,10 @@ def nl2br(x): # In case of a Markup object, make sure to tell it the
we're adding is safe if not isinstance(x, Markup): x = Markup.escape(x) - return x.replace('\n', Markup('
')) + return x.replace("\n", Markup("
")) -SLUGIFY_RE = re.compile('[^a-z0-9_]+') +SLUGIFY_RE = re.compile("[^a-z0-9_]+") @app.template_filter() @@ -1914,25 +2173,25 @@ def slugify(s): """Convert arbitrary text into a text that's safe to use in a URL.""" if s is None: return None - return SLUGIFY_RE.sub('-', strip_accents(s).lower()) + return SLUGIFY_RE.sub("-", strip_accents(s).lower()) @app.template_filter() def chunk(x, size): """Chunk a list into groups of size at most 'size'.""" - return (x[pos:pos + size] for pos in range(0, len(x), size)) + return (x[pos : pos + size] for pos in range(0, len(x), size)) @app.template_global() def hedy_link(level_nr, assignment_nr, subpage=None): """Make a link to a Hedy page.""" - parts = ['/hedy'] - parts.append('/' + str(level_nr)) - if str(assignment_nr) != '1' or subpage: - parts.append('/' + str(assignment_nr if assignment_nr else '1')) - if subpage and subpage != 'code': - parts.append('/' + subpage) - return ''.join(parts) + parts = ["/hedy"] + parts.append("/" + str(level_nr)) + if str(assignment_nr) != "1" or subpage: + parts.append("/" + str(assignment_nr if assignment_nr else "1")) + if subpage and subpage != "code": + parts.append("/" + subpage) + return "".join(parts) @app.template_global() @@ -1944,14 +2203,20 @@ def all_countries(): def other_languages(): """Return a list of language objects that are NOT the current language.""" current_lang = g.lang - return [make_lang_obj(lang) for lang in ALL_LANGUAGES.keys() if lang != current_lang] + return [ + make_lang_obj(lang) for lang in ALL_LANGUAGES.keys() if lang != current_lang + ] @app.template_global() def other_keyword_languages(): """Return a list of language objects that are NOT the current language, and that have translated keywords.""" current_lang = g.lang - return [make_lang_obj(lang) for lang in ALL_KEYWORD_LANGUAGES.keys() if lang != current_lang] + return [ + make_lang_obj(lang) + for lang in ALL_KEYWORD_LANGUAGES.keys() + if lang != current_lang + ] @app.template_global() @@ -1987,18 +2252,12 @@ def parse_keyword(keyword): def make_lang_obj(lang): """Make a language object for a given language.""" - return { - 'sym': ALL_LANGUAGES[lang], - 'lang': lang - } + return {"sym": ALL_LANGUAGES[lang], "lang": lang} def make_keyword_lang_obj(lang): """Make a language object for a given language.""" - return { - 'sym': ALL_KEYWORD_LANGUAGES[lang], - 'lang': lang - } + return {"sym": ALL_KEYWORD_LANGUAGES[lang], "lang": lang} @app.template_global() @@ -2008,19 +2267,19 @@ def modify_query(**new_values): for key, value in new_values.items(): args[key] = value - return '{}?{}'.format(request.path, url_encode(args)) + return "{}?{}".format(request.path, url_encode(args)) @app.template_global() def get_user_messages(): - if not session.get('messages'): + if not session.get("messages"): # Todo TB: In the future this should contain the class invites + other messages # As the class invites are binary (you either have one or you have none, we can possibly simplify this) # Simply set it to 1 if we have an invite, otherwise keep at 0 - invite = DATABASE.get_username_invite(current_user()['username']) - session['messages'] = 1 if invite else 0 - if session.get('messages') > 0: - return session.get('messages') + invite = DATABASE.get_username_invite(current_user()["username"]) + session["messages"] = 1 if invite else 0 + if session.get("messages") > 0: + return session.get("messages") return None @@ -2029,125 +2288,159 @@ def get_user_messages(): # might want to re-write this in the future -@app.route('/auth/public_profile', methods=['POST']) +@app.route("/auth/public_profile", methods=["POST"]) @requires_login def update_public_profile(user): body = request.json # Validations if not isinstance(body, dict): - return gettext('ajax_error'), 400 + return gettext("ajax_error"), 400 # The images are given as a "picture id" from 1 till 12 - if not isinstance(body.get('image'), str) or int(body.get('image'), 0) not in [*range(1, 13)]: - return gettext('image_invalid'), 400 - if not isinstance(body.get('personal_text'), str): - return gettext('personal_text_invalid'), 400 - if 'favourite_program' in body and not isinstance(body.get('favourite_program'), str): - return gettext('favourite_program_invalid'), 400 + if not isinstance(body.get("image"), str) or int(body.get("image"), 0) not in [ + *range(1, 13) + ]: + return gettext("image_invalid"), 400 + if not isinstance(body.get("personal_text"), str): + return gettext("personal_text_invalid"), 400 + if "favourite_program" in body and not isinstance( + body.get("favourite_program"), str + ): + return gettext("favourite_program_invalid"), 400 # Verify that the set favourite program is actually from the user (and public)! - if 'favourite_program' in body: - program = DATABASE.program_by_id(body.get('favourite_program')) - if not program or program.get('username') != user['username'] or not program.get('public'): - return gettext('favourite_program_invalid'), 400 + if "favourite_program" in body: + program = DATABASE.program_by_id(body.get("favourite_program")) + if ( + not program + or program.get("username") != user["username"] + or not program.get("public") + ): + return gettext("favourite_program_invalid"), 400 achievement = None - current_profile = DATABASE.get_public_profile_settings(user['username']) + current_profile = DATABASE.get_public_profile_settings(user["username"]) if current_profile: - if current_profile.get('image') != body.get('image'): + if current_profile.get("image") != body.get("image"): achievement = ACHIEVEMENTS.add_single_achievement( - current_user()['username'], "fresh_look") + current_user()["username"], "fresh_look" + ) else: - achievement = ACHIEVEMENTS.add_single_achievement(current_user()['username'], "go_live") + achievement = ACHIEVEMENTS.add_single_achievement( + current_user()["username"], "go_live" + ) # Make sure the session value for the profile image is up-to-date - session['profile_image'] = body.get('image') + session["profile_image"] = body.get("image") # If there is no current profile or if it doesn't have the tags list -> # check if the user is a teacher / admin - if not current_profile or not current_profile.get('tags'): - body['tags'] = [] + if not current_profile or not current_profile.get("tags"): + body["tags"] = [] if is_teacher(user): - body['tags'].append('teacher') + body["tags"].append("teacher") if is_admin(user): - body['tags'].append('admin') + body["tags"].append("admin") - DATABASE.update_public_profile(user['username'], body) + DATABASE.update_public_profile(user["username"], body) if achievement: # Todo TB -> Check if we require message or success on front-end - return {'message': gettext('public_profile_updated'), 'achievement': achievement}, 200 - return {'message': gettext('public_profile_updated')}, 200 + return { + "message": gettext("public_profile_updated"), + "achievement": achievement, + }, 200 + return {"message": gettext("public_profile_updated")}, 200 -@app.route('/translating') +@app.route("/translating") def translating_page(): - return render_template('translating.html') + return render_template("translating.html") -@app.route('/update_yaml', methods=['POST']) +@app.route("/update_yaml", methods=["POST"]) def update_yaml(): - filename = path.join('coursedata', request.form['file']) + filename = path.join("coursedata", request.form["file"]) # The file MUST point to something inside our 'coursedata' directory filepath = path.abspath(filename) - expected_path = path.abspath('coursedata') + expected_path = path.abspath("coursedata") if not filepath.startswith(expected_path): - raise RuntimeError('Invalid path given') + raise RuntimeError("Invalid path given") data = load_yaml_rt(filepath) for key, value in request.form.items(): - if key.startswith('c:'): + if key.startswith("c:"): translating.apply_form_change( - data, key[2:], translating.normalize_newlines(value)) + data, key[2:], translating.normalize_newlines(value) + ) data = translating.normalize_yaml_blocks(data) - return Response(dump_yaml_rt(data), mimetype='application/x-yaml', - headers={'Content-disposition': 'attachment; filename=' + request.form['file'].replace('/', '-')}) + return Response( + dump_yaml_rt(data), + mimetype="application/x-yaml", + headers={ + "Content-disposition": "attachment; filename=" + + request.form["file"].replace("/", "-") + }, + ) -@app.route('/user/') +@app.route("/user/") def public_user_page(username): - if not current_user()['username']: - return utils.error_page(error=403, ui_message=gettext('unauthorized')) + if not current_user()["username"]: + return utils.error_page(error=403, ui_message=gettext("unauthorized")) username = username.lower() user = DATABASE.user_by_username(username) if not user: - return utils.error_page(error=404, ui_message=gettext('user_not_private')) + return utils.error_page(error=404, ui_message=gettext("user_not_private")) user_public_info = DATABASE.get_public_profile_settings(username) - page = request.args.get('page', default=None, type=str) + page = request.args.get("page", default=None, type=str) if user_public_info: - user_programs = DATABASE.public_programs_for_user(username, - limit=10, - pagination_token=page) + user_programs = DATABASE.public_programs_for_user( + username, limit=10, pagination_token=page + ) next_page_token = user_programs.next_page_token user_programs = normalize_public_programs(user_programs) user_achievements = DATABASE.progress_by_username(username) or {} favourite_program = None - if 'favourite_program' in user_public_info and user_public_info['favourite_program']: + if ( + "favourite_program" in user_public_info + and user_public_info["favourite_program"] + ): favourite_program = DATABASE.program_by_id( - user_public_info['favourite_program']) + user_public_info["favourite_program"] + ) last_achieved = None - if user_achievements.get('achieved'): - last_achieved = user_achievements['achieved'][-1] - certificate_message = safe_format(gettext('see_certificate'), username=username) + if user_achievements.get("achieved"): + last_achieved = user_achievements["achieved"][-1] + certificate_message = safe_format(gettext("see_certificate"), username=username) print(user_programs) # Todo: TB -> In the near future: add achievement for user visiting their own profile - next_page_url = url_for('public_user_page', username=username, **dict(request.args, - page=next_page_token)) if next_page_token else None + next_page_url = ( + url_for( + "public_user_page", + username=username, + **dict(request.args, page=next_page_token), + ) + if next_page_token + else None + ) return render_template( - 'public-page.html', + "public-page.html", user_info=user_public_info, - achievements=ACHIEVEMENTS_TRANSLATIONS.get_translations( - g.lang).get('achievements'), + achievements=ACHIEVEMENTS_TRANSLATIONS.get_translations(g.lang).get( + "achievements" + ), favourite_program=favourite_program, programs=user_programs, last_achieved=last_achieved, user_achievements=user_achievements, certificate_message=certificate_message, - next_page_url=next_page_url) - return utils.error_page(error=404, ui_message=gettext('user_not_private')) + next_page_url=next_page_url, + ) + return utils.error_page(error=404, ui_message=gettext("user_not_private")) def valid_invite_code(code): @@ -2157,30 +2450,32 @@ def valid_invite_code(code): # Get the value from the environment, use literal_eval to convert from # string list to an actual list valid_codes = [] - if os.getenv('TEACHER_INVITE_CODE'): - valid_codes.append(os.getenv('TEACHER_INVITE_CODE')) - if os.getenv('TEACHER_INVITE_CODES'): - valid_codes.extend(os.getenv('TEACHER_INVITE_CODES').split(',')) + if os.getenv("TEACHER_INVITE_CODE"): + valid_codes.append(os.getenv("TEACHER_INVITE_CODE")) + if os.getenv("TEACHER_INVITE_CODES"): + valid_codes.extend(os.getenv("TEACHER_INVITE_CODES").split(",")) return code in valid_codes -@app.route('/invite/', methods=['GET']) +@app.route("/invite/", methods=["GET"]) def teacher_invitation(code): user = current_user() if not valid_invite_code(code): - return utils.error_page(error=404, ui_message=gettext('invalid_teacher_invitation_code')) + return utils.error_page( + error=404, ui_message=gettext("invalid_teacher_invitation_code") + ) - if not user['username']: - return render_template('teacher-invitation.html') + if not user["username"]: + return render_template("teacher-invitation.html") admin.update_is_teacher(DATABASE, user) # When visiting this link we update the current user to a teacher -> also update user in session - session.get('user')['is_teacher'] = True + session.get("user")["is_teacher"] = True - session['welcome-teacher'] = True - url = request.url.replace(f'/invite/{code}', '/for-teachers') + session["welcome-teacher"] = True + url = request.url.replace(f"/invite/{code}", "/for-teachers") return redirect(url) @@ -2193,14 +2488,16 @@ def current_user_allowed_to_see_program(program): user = current_user() # These are all easy - if program.get('public'): + if program.get("public"): return True - if user['username'] == program['username']: + if user["username"] == program["username"]: return True if is_admin(user): return True - if is_teacher(user) and program['username'] in DATABASE.get_teacher_students(user['username']): + if is_teacher(user) and program["username"] in DATABASE.get_teacher_students( + user["username"] + ): return True return False @@ -2238,20 +2535,21 @@ def try_parse_int(x): return None -if __name__ == '__main__': +if __name__ == "__main__": # Start the server on a developer machine. Flask is initialized in DEBUG mode, so it # hot-reloads files. We also flip our own internal "debug mode" flag to True, so our # own file loading routines also hot-reload. - utils.set_debug_mode(not os.getenv('NO_DEBUG_MODE')) + utils.set_debug_mode(not os.getenv("NO_DEBUG_MODE")) # If we are running in a Python debugger, don't use flasks reload mode. It creates # subprocesses which make debugging harder. is_in_debugger = sys.gettrace() is not None on_server_start() - logger.debug('app starting in debug mode') + logger.debug("app starting in debug mode") # Threaded option enables multiple instances for multiple user access support - app.run(threaded=True, debug=not is_in_debugger, - port=config['port'], host="0.0.0.0") + app.run( + threaded=True, debug=not is_in_debugger, port=config["port"], host="0.0.0.0" + ) # See `Procfile` for how the server is started on Heroku. diff --git a/build-tools/github/validate-pofiles b/build-tools/github/validate-pofiles index 40bb07748cd..d05d8180ebe 100755 --- a/build-tools/github/validate-pofiles +++ b/build-tools/github/validate-pofiles @@ -13,7 +13,7 @@ def scan_pofiles(root): ret = False for dir, _, files in os.walk(root): for file in files: - if file.endswith('.po'): + if file.endswith(".po"): fullpath = path.join(dir, file) ret |= scan_file(fullpath) return ret @@ -23,7 +23,7 @@ PAT = re.compile('^(?:#~ )?msgid "(.*)"') def scan_file(filename): - with open(filename, 'r', encoding='utf-8') as f: + with open(filename, "r", encoding="utf-8") as f: lines = f.read().splitlines() indexes = collections.defaultdict(list) @@ -36,25 +36,27 @@ def scan_file(filename): if len(morethanonce) == 0: return False - print('-' * 70) + print("-" * 70) print(filename) - print('-' * 70) + print("-" * 70) for word, linenrs in morethanonce: print(f"The translation of '{word}' occurs {len(linenrs)} times:") - print('') + print("") for nr in linenrs: - print(f' {nr + 1}: {lines[nr]}') - print(f' {nr + 2}: {lines[nr + 1]}') - print('') + print(f" {nr + 1}: {lines[nr]}") + print(f" {nr + 2}: {lines[nr + 1]}") + print("") return True -if __name__ == '__main__': - fail = scan_pofiles(path.join(path.dirname(__file__), '..', '..', 'translations')) +if __name__ == "__main__": + fail = scan_pofiles(path.join(path.dirname(__file__), "..", "..", "translations")) if fail: - print('Something went wrong with the gettext translation files, some entries were duplicated.') - print('See above for the list. Please make sure every msgid occurs only once,') - print('not commented out, with the right translation.') + print( + "Something went wrong with the gettext translation files, some entries were duplicated." + ) + print("See above for the list. Please make sure every msgid occurs only once,") + print("not commented out, with the right translation.") sys.exit(1) diff --git a/build-tools/heroku/generate-client-messages.py b/build-tools/heroku/generate-client-messages.py index e5bd8fac872..fe2b9c35ce6 100644 --- a/build-tools/heroku/generate-client-messages.py +++ b/build-tools/heroku/generate-client-messages.py @@ -9,21 +9,22 @@ # Import packages from the website app (AutoPep8 will mess this up, so disable it) import sys -sys.path.append(path.abspath(path.join(path.dirname(__file__), '..', '..'))) # noqa + +sys.path.append(path.abspath(path.join(path.dirname(__file__), "..", ".."))) # noqa from website.yaml_file import YamlFile # noqa -OUTPUT_FILE = 'static/js/message-translations.ts' +OUTPUT_FILE = "static/js/message-translations.ts" # Every key you add here must also be added to content/client-messages.txt ADDITIONAL_GETTEXT_KEYS = [ - 'level_title', - 'unsaved_class_changes', - 'teacher_welcome', - 'copy_link_to_share', - 'customization_deleted', - 'directly_available', - 'disabled', - 'adventures_restored' + "level_title", + "unsaved_class_changes", + "teacher_welcome", + "copy_link_to_share", + "customization_deleted", + "directly_available", + "disabled", + "adventures_restored", ] @@ -34,14 +35,14 @@ def main(): message_keys = set() # Client messages - for cm_file in sorted(glob.glob('content/client-messages/*.yaml')): + for cm_file in sorted(glob.glob("content/client-messages/*.yaml")): lang_code = path.splitext(path.basename(cm_file))[0] cm = YamlFile.for_file(cm_file).to_dict() for key, value in list(cm.items()): # Make an array of strings into a single string, joined by newline if isinstance(value, list): - cm[key] = '\n'.join(value) + cm[key] = "\n".join(value) translations[lang_code] = cm message_keys |= cm.keys() @@ -49,7 +50,9 @@ def main(): # Gettext translations for lang, trans in translations.items(): try: - translator = gettext.translation('messages', localedir='translations', languages=[lang, 'en']) + translator = gettext.translation( + "messages", localedir="translations", languages=[lang, "en"] + ) for key in ADDITIONAL_GETTEXT_KEYS: trans[key] = translator.gettext(key) message_keys.add(key) @@ -57,13 +60,22 @@ def main(): raise RuntimeError(f"Not able to load translations for '{lang}'") from e # Write file - with open(OUTPUT_FILE, 'w', encoding='utf-8') as f: - f.write('// This file has been generated by build-tools/heroku/generate-client-messages.py\n') - f.write('// DO NOT EDIT\n') - f.write('\n') - f.write('export type MessageKey = ' + ' | '.join(f"'{k}'" for k in sorted(message_keys)) + '\n') - f.write('export const TRANSLATIONS: Record> = ' + - json.dumps(translations, indent=2, sort_keys=True, ensure_ascii=False) + ';\n') + with open(OUTPUT_FILE, "w", encoding="utf-8") as f: + f.write( + "// This file has been generated by build-tools/heroku/generate-client-messages.py\n" + ) + f.write("// DO NOT EDIT\n") + f.write("\n") + f.write( + "export type MessageKey = " + + " | ".join(f"'{k}'" for k in sorted(message_keys)) + + "\n" + ) + f.write( + "export const TRANSLATIONS: Record> = " + + json.dumps(translations, indent=2, sort_keys=True, ensure_ascii=False) + + ";\n" + ) def validate_gettext_hackfile(): @@ -71,9 +83,11 @@ def validate_gettext_hackfile(): If we don't do this, babel will not find the keys when it does an extract. """ - filename = path.join(path.dirname(__file__), '..', '..', 'content', 'client-messages.txt') + filename = path.join( + path.dirname(__file__), "..", "..", "content", "client-messages.txt" + ) with open(filename) as f: - messages = set(x.strip() for x in f.read().split('\n')) + messages = set(x.strip() for x in f.read().split("\n")) oopsie = False @@ -81,11 +95,13 @@ def validate_gettext_hackfile(): gettext_line = f"gettext('{additional_key}')" if gettext_line not in messages: oopsie = True - print(f'Put the following line into content/client-messages.txt: {gettext_line}') + print( + f"Put the following line into content/client-messages.txt: {gettext_line}" + ) if oopsie: sys.exit(1) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/build-tools/heroku/generate-static-babel-content b/build-tools/heroku/generate-static-babel-content index 7e9ed6eb7b7..61186743f18 100755 --- a/build-tools/heroku/generate-static-babel-content +++ b/build-tools/heroku/generate-static-babel-content @@ -19,27 +19,32 @@ from babel import Locale, languages, UnknownLocaleError def main(): - root_dir = path.abspath(path.join(path.dirname(__file__), '..', '..')) - filename = path.join(root_dir, 'static_babel_content.py') - - translated_languages = [lang for lang in os.listdir(path.join(root_dir, 'translations')) if path.isdir( - path.join(root_dir, 'translations', lang, 'LC_MESSAGES'))] - - with open(filename, 'w', encoding='utf-8') as f: - f.write('# coding=utf-8\n') - f.write('# flake8: noqa\n') - f.write('###################################################\n') - f.write('#\n') - f.write('# !!! THIS FILE HAS BEEN GENERATED. DO NOT EDIT !!!\n') - f.write('#\n') - f.write('# Run build-tools/heroku/generate-static-babel-content to regenerate.\n') - f.write('#\n') - f.write('###################################################\n') - f.write('\n') - f.write('COUNTRIES = %r\n' % countries()) - f.write('LANGUAGE_NAMES = %r\n' % language_names(translated_languages)) - f.write('TEXT_DIRECTIONS = %r\n' % text_directions(translated_languages)) - print('Wrote', filename) + root_dir = path.abspath(path.join(path.dirname(__file__), "..", "..")) + filename = path.join(root_dir, "static_babel_content.py") + + translated_languages = [ + lang + for lang in os.listdir(path.join(root_dir, "translations")) + if path.isdir(path.join(root_dir, "translations", lang, "LC_MESSAGES")) + ] + + with open(filename, "w", encoding="utf-8") as f: + f.write("# coding=utf-8\n") + f.write("# flake8: noqa\n") + f.write("###################################################\n") + f.write("#\n") + f.write("# !!! THIS FILE HAS BEEN GENERATED. DO NOT EDIT !!!\n") + f.write("#\n") + f.write( + "# Run build-tools/heroku/generate-static-babel-content to regenerate.\n" + ) + f.write("#\n") + f.write("###################################################\n") + f.write("\n") + f.write("COUNTRIES = %r\n" % countries()) + f.write("LANGUAGE_NAMES = %r\n" % language_names(translated_languages)) + f.write("TEXT_DIRECTIONS = %r\n" % text_directions(translated_languages)) + print("Wrote", filename) def countries(): @@ -86,5 +91,5 @@ def locale(lang): pass -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/config.py b/config.py index 89a07a5ba81..9c31f5b9cff 100644 --- a/config.py +++ b/config.py @@ -1,49 +1,47 @@ import os import socket -app_name = os.getenv('HEROKU_APP_NAME', socket.gethostname()) -dyno = os.getenv('DYNO') -athena_query = os.getenv('AWS_ATHENA_PREPARE_STATEMENT') +app_name = os.getenv("HEROKU_APP_NAME", socket.gethostname()) +dyno = os.getenv("DYNO") +athena_query = os.getenv("AWS_ATHENA_PREPARE_STATEMENT") config = { - 'port': os.getenv('PORT') or 8080, - 'session': { - 'cookie_name': 'hedy', + "port": os.getenv("PORT") or 8080, + "session": { + "cookie_name": "hedy", # in minutes - 'session_length': 60 * 24 * 14, - 'reset_length': 60 * 4, - 'invite_length': 60 * 24 * 7 + "session_length": 60 * 24 * 14, + "reset_length": 60 * 4, + "invite_length": 60 * 24 * 7, }, - 'email': { - 'sender': 'Hedy ', - 'region': 'eu-central-1', + "email": { + "sender": "Hedy ", + "region": "eu-central-1", }, # The bcrypt library's default is 12 - 'bcrypt_rounds': 9, - 'dynamodb': { - 'region': 'eu-west-1' - }, - 's3-query-logs': { - 'bucket': 'hedy-query-logs', - 'prefix': app_name + '/', + "bcrypt_rounds": 9, + "dynamodb": {"region": "eu-west-1"}, + "s3-query-logs": { + "bucket": "hedy-query-logs", + "prefix": app_name + "/", # Make logs from different instances/processes unique - 'postfix': ('-' + dyno if dyno else '') + '-' + str(os.getpid()), - 'region': 'eu-west-1' + "postfix": ("-" + dyno if dyno else "") + "-" + str(os.getpid()), + "region": "eu-west-1", }, - 's3-parse-logs': { - 'bucket': 'hedy-parse-logs', - 'prefix': app_name + '/', + "s3-parse-logs": { + "bucket": "hedy-parse-logs", + "prefix": app_name + "/", # Make logs from different instances/processes unique - 'postfix': ('-' + dyno if dyno else '') + '-' + str(os.getpid()), - 'region': 'eu-west-1' + "postfix": ("-" + dyno if dyno else "") + "-" + str(os.getpid()), + "region": "eu-west-1", }, - 'athena': { - 'region': 'eu-west-1', - 'database': 'hedy-logs', - 'prepare_statement': athena_query, - 's3_output': 's3://hedy-query-outputs/', - 'max_results': 50 + "athena": { + "region": "eu-west-1", + "database": "hedy-logs", + "prepare_statement": athena_query, + "s3_output": "s3://hedy-query-outputs/", + "max_results": 50, }, # enables the quiz environment by setting the config variable on True - 'quiz-enabled': True, + "quiz-enabled": True, } diff --git a/content/yaml_to_lark_utils.py b/content/yaml_to_lark_utils.py index 5caab4af7c5..9d995fe8d0e 100644 --- a/content/yaml_to_lark_utils.py +++ b/content/yaml_to_lark_utils.py @@ -13,24 +13,27 @@ def extract_Lark_grammar_from_yaml(): or for all languages. Defaults to True. """ dirname = os.path.dirname(__file__) - input_path = os.path.join(dirname, 'keywords') - current_grammar_path = os.path.join(dirname, '../grammars') + input_path = os.path.join(dirname, "keywords") + current_grammar_path = os.path.join(dirname, "../grammars") - yaml_languages = [f.replace('.yaml', '') for f in os.listdir(input_path) if - os.path.isfile(os.path.join(input_path, f)) and f.endswith('.yaml')] + yaml_languages = [ + f.replace(".yaml", "") + for f in os.listdir(input_path) + if os.path.isfile(os.path.join(input_path, f)) and f.endswith(".yaml") + ] - template_lark = os.path.join(current_grammar_path, 'keywords-template.lark') - with open(template_lark, 'r', encoding='utf-8') as f: + template_lark = os.path.join(current_grammar_path, "keywords-template.lark") + with open(template_lark, "r", encoding="utf-8") as f: template = f.read() for yaml_lang in yaml_languages: - yaml_filesname_with_path = os.path.join(input_path, yaml_lang + '.yaml') - default_yaml_with_path = os.path.join(input_path, 'en' + '.yaml') + yaml_filesname_with_path = os.path.join(input_path, yaml_lang + ".yaml") + default_yaml_with_path = os.path.join(input_path, "en" + ".yaml") - with open(default_yaml_with_path, 'r', encoding='utf-8') as stream: + with open(default_yaml_with_path, "r", encoding="utf-8") as stream: en_command_combinations = yaml.safe_load(stream) - with open(yaml_filesname_with_path, 'r', encoding='utf-8') as stream: + with open(yaml_filesname_with_path, "r", encoding="utf-8") as stream: command_combinations = yaml.safe_load(stream) # We don't want to create the grammar if there are no translations @@ -38,14 +41,16 @@ def extract_Lark_grammar_from_yaml(): continue # Create an empty dictionary -> fill with english keywords and then overwrite all translated keywords - translations = collections.defaultdict(lambda: 'Unknown Exception') + translations = collections.defaultdict(lambda: "Unknown Exception") translations.update(en_command_combinations) translations.update(command_combinations) translation_copy = copy.deepcopy(translations) for k, v in translation_copy.items(): if yaml_lang == "ar": - mixed_tatweel_in = ''.join([' "ـ"* ' + '"' + lang + '"' for lang in v]) + ' "ـ"* ' + mixed_tatweel_in = ( + "".join([' "ـ"* ' + '"' + lang + '"' for lang in v]) + ' "ـ"* ' + ) translations[k] = mixed_tatweel_in else: # other languages need their translations surrounded by "'s @@ -55,14 +60,18 @@ def extract_Lark_grammar_from_yaml(): if "|" in v: valid_translation = "" options = v.split("|") - valid_translation = ' | '.join(['"' + option + '"' for option in options]) + valid_translation = " | ".join( + ['"' + option + '"' for option in options] + ) translations[k] = valid_translation translated_template = template.format(**translations) - lark_filesname_with_path = os.path.join(current_grammar_path, 'keywords-' + yaml_lang + '.lark') + lark_filesname_with_path = os.path.join( + current_grammar_path, "keywords-" + yaml_lang + ".lark" + ) - with open(lark_filesname_with_path, 'w', encoding='utf-8') as f: + with open(lark_filesname_with_path, "w", encoding="utf-8") as f: f.write(translated_template) diff --git a/docs.py b/docs.py index a0a99799ad2..c3f8e4f4863 100644 --- a/docs.py +++ b/docs.py @@ -7,12 +7,13 @@ def slugify(s): if s is None: return None - return re.sub('[^a-zA-Z0-9]', '-', strip_accents(s)).lower() + return re.sub("[^a-zA-Z0-9]", "-", strip_accents(s)).lower() def strip_accents(s): - return ''.join(c for c in unicodedata.normalize('NFD', s) - if unicodedata.category(c) != 'Mn') + return "".join( + c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn" + ) class DocCollection: @@ -31,7 +32,7 @@ def get(self, *keys): return v def load_dir(self, rootdir): - files = glob.glob(f'{rootdir}/**/*.md', recursive=True) + files = glob.glob(f"{rootdir}/**/*.md", recursive=True) for file in sorted(files): doc = MarkdownDoc.from_file(file) @@ -61,9 +62,9 @@ def add_to_index(self, doc): class MarkdownDoc: @staticmethod def from_file(filename): - with open(filename, 'r', encoding='utf-8') as f: + with open(filename, "r", encoding="utf-8") as f: contents = f.read() - parts = re.split('^---+$', contents, maxsplit=1, flags=re.M) + parts = re.split("^---+$", contents, maxsplit=1, flags=re.M) if len(parts) == 1: return MarkdownDoc({}, parts[0]) return MarkdownDoc(yaml.safe_load(parts[0]), parts[1]) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 81509625920..2a94fff8c0a 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -243,12 +243,12 @@ As this project is growing and multiple people are working on it, we want to mov (.env) $ pre-commit install ``` -After this, every modification you commit will be linted by flake8 according to the configuration in setup.cfg. If there are any issues with your code, you can fix these manually using the output, or alternatively use autopep8 to solve these issues automatically (although autopep8 can't fix some issues). If you want to do this, install autopep8 using `pip install autopep8` and run `autopep8 --in-place --max-line-length=120 [your-file]`. +After this, every modification you commit will be linted by flake8 and autoformatted by black according to the configuration in setup.cfg. If there are any issues with your code, you can fix these manually using the output, or alternatively use black to solve some formatting issues automatically. If you want to do this, install black using `pip install black` and run `black [your-file]`. If you want, you can bypass the pre-commit check by adding a no-verify flag: `git commit -m "your message" --no-verify` -When you push code to the repository or make a pull request, a Github Actions workflow will also automatically check your code. At the moment failing this check does not prevent from merging, as there is still some work to do to make the entire codebase compliant. However, it is appreciated if your modifications of new code follow PEP8 styling guidelines. Keep the Boy Scout Rule in mind: always leave the code better than you found it! +When you push code to the repository or make a pull request, a Github Actions workflow will also automatically check your code. When seeing any kind of improvement, please keep the Boy Scout Rule in mind: always leave the code better than you found it! ## Working on the web front-end in TypeScript/JavaScript Part of the code base of Hedy is written in Python, which runs on the server. diff --git a/exceptions.py b/exceptions.py index 2a3e3ccd7b8..fc4094577aa 100644 --- a/exceptions.py +++ b/exceptions.py @@ -26,10 +26,10 @@ def error_location(self): wrapped in a list so we are sure the return type is always a list. """ - if 'location' in self.arguments: - return self.arguments['location'] - if 'line_number' in self.arguments: - return [self.arguments['line_number']] + if "location" in self.arguments: + return self.arguments["location"] + if "line_number" in self.arguments: + return [self.arguments["line_number"]] return None @@ -51,21 +51,25 @@ def __init__(self, error_code, fixed_code, fixed_result, **arguments): class InvalidSpaceException(WarningException): def __init__(self, level, line_number, fixed_code, fixed_result): - super().__init__('Invalid Space', - level=level, - line_number=line_number, - fixed_code=fixed_code, - fixed_result=fixed_result) + super().__init__( + "Invalid Space", + level=level, + line_number=line_number, + fixed_code=fixed_code, + fixed_result=fixed_result, + ) class ParseException(HedyException): def __init__(self, level, location, found, fixed_code=None): - super().__init__('Parse', - level=level, - location=location, - found=found, - # 'character_found' for backwards compatibility - character_found=found) + super().__init__( + "Parse", + level=level, + location=location, + found=found, + # 'character_found' for backwards compatibility + character_found=found, + ) # TODO (FH, 8 dec 21) many exceptions now support fixed code maybe we # should move it to hedyexception? @@ -74,143 +78,151 @@ def __init__(self, level, location, found, fixed_code=None): class UnquotedEqualityCheck(HedyException): def __init__(self, line_number): - super().__init__('Unquoted Equality Check', - line_number=line_number) + super().__init__("Unquoted Equality Check", line_number=line_number) self.location = [line_number] class AccessBeforeAssign(HedyException): def __init__(self, name, access_line_number, definition_line_number): - super().__init__('Access Before Assign', - name=name, - access_line_number=access_line_number, - line_number=access_line_number, - definition_line_number=definition_line_number) + super().__init__( + "Access Before Assign", + name=name, + access_line_number=access_line_number, + line_number=access_line_number, + definition_line_number=definition_line_number, + ) class UndefinedVarException(HedyException): def __init__(self, name, line_number): - super().__init__('Var Undefined', - name=name, - line_number=line_number) + super().__init__("Var Undefined", name=name, line_number=line_number) class CyclicVariableDefinitionException(HedyException): def __init__(self, variable, line_number): - super().__init__('Cyclic Var Definition', - variable=variable, - line_number=line_number) + super().__init__( + "Cyclic Var Definition", variable=variable, line_number=line_number + ) class InvalidArgumentTypeException(HedyException): - def __init__(self, command, invalid_type, allowed_types, invalid_argument, line_number): - super().__init__('Invalid Argument Type', - command=command, - invalid_type=invalid_type, - allowed_types=allowed_types, - invalid_argument=invalid_argument, - line_number=line_number) + def __init__( + self, command, invalid_type, allowed_types, invalid_argument, line_number + ): + super().__init__( + "Invalid Argument Type", + command=command, + invalid_type=invalid_type, + allowed_types=allowed_types, + invalid_argument=invalid_argument, + line_number=line_number, + ) class InvalidTypeCombinationException(HedyException): def __init__(self, command, arg1, arg2, type1, type2, line_number): - super().__init__('Invalid Type Combination', - command=command, - invalid_argument=arg1, - invalid_argument_2=arg2, - invalid_type=type1, - invalid_type_2=type2, - line_number=line_number) + super().__init__( + "Invalid Type Combination", + command=command, + invalid_argument=arg1, + invalid_argument_2=arg2, + invalid_type=type1, + invalid_type_2=type2, + line_number=line_number, + ) class InvalidArgumentException(HedyException): def __init__(self, command, allowed_types, invalid_argument, line_number): - super().__init__('Invalid Argument', - command=command, - allowed_types=allowed_types, - invalid_argument=invalid_argument, - line_number=line_number) + super().__init__( + "Invalid Argument", + command=command, + allowed_types=allowed_types, + invalid_argument=invalid_argument, + line_number=line_number, + ) class WrongLevelException(HedyException): def __init__(self, working_level, offending_keyword, tip, line_number): - super().__init__('Wrong Level', - working_level=working_level, - offending_keyword=offending_keyword, - tip=tip, - line_number=line_number) + super().__init__( + "Wrong Level", + working_level=working_level, + offending_keyword=offending_keyword, + tip=tip, + line_number=line_number, + ) class InputTooBigException(HedyException): def __init__(self, lines_of_code, max_lines): - super().__init__('Too Big', - lines_of_code=lines_of_code, - max_lines=max_lines) + super().__init__("Too Big", lines_of_code=lines_of_code, max_lines=max_lines) class InvalidCommandException(WarningException): def __init__( - self, - level, - invalid_command, - guessed_command, - line_number, - fixed_code, - fixed_result): - super().__init__('Invalid', - invalid_command=invalid_command, - level=level, - guessed_command=guessed_command, - line_number=line_number, - fixed_code=fixed_code, - fixed_result=fixed_result) + self, + level, + invalid_command, + guessed_command, + line_number, + fixed_code, + fixed_result, + ): + super().__init__( + "Invalid", + invalid_command=invalid_command, + level=level, + guessed_command=guessed_command, + line_number=line_number, + fixed_code=fixed_code, + fixed_result=fixed_result, + ) self.location = [line_number] class MissingCommandException(HedyException): def __init__(self, level, line_number): - super().__init__('Missing Command', - level=level, - line_number=line_number) + super().__init__("Missing Command", level=level, line_number=line_number) class MissingInnerCommandException(HedyException): def __init__(self, command, level, line_number): - super().__init__('Missing Inner Command', - command=command, - level=level, - line_number=line_number) + super().__init__( + "Missing Inner Command", + command=command, + level=level, + line_number=line_number, + ) class InvalidAtCommandException(HedyException): def __init__(self, command, level, line_number): - super().__init__('Invalid At Command', - command=command, - level=level, - line_number=line_number) + super().__init__( + "Invalid At Command", command=command, level=level, line_number=line_number + ) class IncompleteRepeatException(HedyException): def __init__(self, command, level, line_number): - super().__init__('Incomplete Repeat', - command=command, - level=level, - line_number=line_number) + super().__init__( + "Incomplete Repeat", command=command, level=level, line_number=line_number + ) class LonelyTextException(HedyException): def __init__(self, level, line_number): - super().__init__('Lonely Text', - level=level, - line_number=line_number) + super().__init__("Lonely Text", level=level, line_number=line_number) class IncompleteCommandException(HedyException): def __init__(self, incomplete_command, level, line_number): - super().__init__('Incomplete', - incomplete_command=incomplete_command, - level=level, - line_number=line_number) + super().__init__( + "Incomplete", + incomplete_command=incomplete_command, + level=level, + line_number=line_number, + ) # Location is copied here so that 'hedy_error_to_response' will find it # Location can be either [row, col] or just [row] @@ -219,68 +231,76 @@ def __init__(self, incomplete_command, level, line_number): class UnquotedTextException(HedyException): def __init__(self, level, line_number, unquotedtext=None): - super().__init__('Unquoted Text', - level=level, - unquotedtext=unquotedtext, - line_number=line_number) + super().__init__( + "Unquoted Text", + level=level, + unquotedtext=unquotedtext, + line_number=line_number, + ) class UnquotedAssignTextException(HedyException): def __init__(self, text, line_number): - super().__init__('Unquoted Assignment', text=text, line_number=line_number) + super().__init__("Unquoted Assignment", text=text, line_number=line_number) class LonelyEchoException(HedyException): def __init__(self): - super().__init__('Lonely Echo') + super().__init__("Lonely Echo") class CodePlaceholdersPresentException(HedyException): def __init__(self, line_number): - super().__init__('Has Blanks', line_number=line_number) + super().__init__("Has Blanks", line_number=line_number) class NoIndentationException(HedyException): def __init__(self, line_number, leading_spaces, indent_size, fixed_code=None): - super().__init__('No Indentation', - line_number=line_number, - leading_spaces=leading_spaces, - indent_size=indent_size) + super().__init__( + "No Indentation", + line_number=line_number, + leading_spaces=leading_spaces, + indent_size=indent_size, + ) self.fixed_code = fixed_code class IndentationException(HedyException): def __init__(self, line_number, leading_spaces, indent_size, fixed_code=None): - super().__init__('Unexpected Indentation', - line_number=line_number, - leading_spaces=leading_spaces, - indent_size=indent_size) + super().__init__( + "Unexpected Indentation", + line_number=line_number, + leading_spaces=leading_spaces, + indent_size=indent_size, + ) self.fixed_code = fixed_code class UnsupportedFloatException(HedyException): def __init__(self, value): - super().__init__('Unsupported Float', value=value) + super().__init__("Unsupported Float", value=value) class LockedLanguageFeatureException(HedyException): def __init__(self, concept): - super().__init__('Locked Language Feature', concept=concept) + super().__init__("Locked Language Feature", concept=concept) class UnsupportedStringValue(HedyException): def __init__(self, invalid_value): - super().__init__('Unsupported String Value', invalid_value=invalid_value) + super().__init__("Unsupported String Value", invalid_value=invalid_value) class MissingElseForPressitException(HedyException): def __init__(self, command, level, line_number): - super().__init__('Pressit Missing Else', - command=command, - level=level, - line_number=line_number) + super().__init__( + "Pressit Missing Else", + command=command, + level=level, + line_number=line_number, + ) class NestedFunctionException(HedyException): def __init__(self): - super().__init__('Nested Function') + super().__init__("Nested Function") diff --git a/gunicorn.conf.py b/gunicorn.conf.py index 6656f707245..2ff0005577d 100644 --- a/gunicorn.conf.py +++ b/gunicorn.conf.py @@ -1,10 +1,12 @@ # This file is used to configure gunicorn, # used on Heroku. + def worker_exit(server, worker): # When the worker is being exited (perhaps because of a timeout), # give the query_log handler a chance to flush to disk. from website import querylog, s3_logger + querylog.emergency_shutdown() s3_logger.emergency_shutdown() @@ -12,4 +14,5 @@ def worker_exit(server, worker): def post_fork(server, worker): """When the worker has started.""" import app + app.on_server_start() diff --git a/hedy.py b/hedy.py index ef3aa3a80b8..f88e6174484 100644 --- a/hedy.py +++ b/hedy.py @@ -40,111 +40,113 @@ # builtins taken from 3.11.0 docs: https://docs.python.org/3/library/functions.html PYTHON_BUILTIN_FUNCTIONS = [ - 'abs', - 'aiter', - 'all', - 'any', - 'anext', - 'ascii', - 'bin', - 'bool', - 'breakpoint', - 'bytearray', - 'bytes', - 'callable', - 'chr', - 'classmethod', - 'compile', - 'complex', - 'delattr', - 'dict', - 'dir', - 'divmod', - 'enumerate', - 'eval', - 'exec', - 'filter', - 'float', - 'format', - 'frozenset', - 'getattr', - 'globals', - 'hasattr', - 'hash', - 'help', - 'hex', - 'id', - 'input', - 'int', - 'isinstance', - 'issubclass', - 'iter', - 'len', - 'list', - 'locals', - 'map', - 'max', - 'memoryview', - 'min', - 'next', - 'object', - 'oct', - 'open', - 'ord', - 'pow', - 'print', - 'property', - 'range', - 'repr', - 'reversed', - 'round', - 'set', - 'setattr', - 'slice', - 'sorted', - 'staticmethod', - 'str', - 'sum', - 'super', - 'tuple', - 'type', - 'vars', - 'zip'] + "abs", + "aiter", + "all", + "any", + "anext", + "ascii", + "bin", + "bool", + "breakpoint", + "bytearray", + "bytes", + "callable", + "chr", + "classmethod", + "compile", + "complex", + "delattr", + "dict", + "dir", + "divmod", + "enumerate", + "eval", + "exec", + "filter", + "float", + "format", + "frozenset", + "getattr", + "globals", + "hasattr", + "hash", + "help", + "hex", + "id", + "input", + "int", + "isinstance", + "issubclass", + "iter", + "len", + "list", + "locals", + "map", + "max", + "memoryview", + "min", + "next", + "object", + "oct", + "open", + "ord", + "pow", + "print", + "property", + "range", + "repr", + "reversed", + "round", + "set", + "setattr", + "slice", + "sorted", + "staticmethod", + "str", + "sum", + "super", + "tuple", + "type", + "vars", + "zip", +] PYTHON_KEYWORDS = [ - 'and', - 'except', - 'lambda', - 'with', - 'as', - 'finally', - 'nonlocal', - 'while', - 'assert', - 'False', - 'None', - 'yield', - 'break', - 'for', - 'not', - 'class', - 'from', - 'or', - 'continue', - 'global', - 'pass', - 'def', - 'if', - 'raise', - 'del', - 'import', - 'return', - 'elif', - 'in', - 'True', - 'else', - 'is', - 'try', - 'int'] + "and", + "except", + "lambda", + "with", + "as", + "finally", + "nonlocal", + "while", + "assert", + "False", + "None", + "yield", + "break", + "for", + "not", + "class", + "from", + "or", + "continue", + "global", + "pass", + "def", + "if", + "raise", + "del", + "import", + "return", + "elif", + "in", + "True", + "else", + "is", + "try", + "int", +] # Python keywords and function names need hashing when used as var names reserved_words = set(PYTHON_BUILTIN_FUNCTIONS + PYTHON_KEYWORDS) @@ -152,7 +154,7 @@ indent_keywords = {} for lang, keywords in KEYWORDS.items(): indent_keywords[lang] = [] - for keyword in ['if', 'elif', 'for', 'repeat', 'while', 'else', 'define', 'def']: + for keyword in ["if", "elif", "for", "repeat", "while", "else", "define", "def"]: indent_keywords[lang].append(keyword) # always also check for En indent_keywords[lang].append(keywords.get(keyword)) @@ -163,76 +165,76 @@ def needs_colon(rule): - pos = rule.find('_EOL (_SPACE command)') - return f'{rule[0:pos]} _COLON {rule[pos:]}' + pos = rule.find("_EOL (_SPACE command)") + return f"{rule[0:pos]} _COLON {rule[pos:]}" -PREPROCESS_RULES = { - 'needs_colon': needs_colon -} +PREPROCESS_RULES = {"needs_colon": needs_colon} class Command: - print = 'print' - ask = 'ask' - echo = 'echo' - turn = 'turn' - forward = 'forward' - sleep = 'sleep' - color = 'color' - add_to_list = 'add to list' - remove_from_list = 'remove from list' - list_access = 'at random' - in_list = 'in list' - equality = 'is (equality)' - repeat = 'repeat' - for_list = 'for in' - for_loop = 'for in range' - addition = '+' - subtraction = '-' - multiplication = '*' - division = '/' - smaller = '<' - smaller_equal = '<=' - bigger = '>' - bigger_equal = '>=' - not_equal = '!=' - pressed = 'pressed' - clear = 'clear' - define = 'define' - call = 'call' - returns = 'return' - - -translatable_commands = {Command.print: ['print'], - Command.ask: ['ask'], - Command.echo: ['echo'], - Command.turn: ['turn'], - Command.sleep: ['sleep'], - Command.color: ['color'], - Command.forward: ['forward'], - Command.add_to_list: ['add', 'to_list'], - Command.remove_from_list: ['remove', 'from'], - Command.list_access: ['at', 'random'], - Command.in_list: ['in'], - Command.equality: ['is', '=', '=='], - Command.repeat: ['repeat', 'times'], - Command.for_list: ['for', 'in'], - Command.for_loop: ['in', 'range', 'to'], - Command.define: ['define'], - Command.call: ['call'], - Command.returns: ['return'], } + print = "print" + ask = "ask" + echo = "echo" + turn = "turn" + forward = "forward" + sleep = "sleep" + color = "color" + add_to_list = "add to list" + remove_from_list = "remove from list" + list_access = "at random" + in_list = "in list" + equality = "is (equality)" + repeat = "repeat" + for_list = "for in" + for_loop = "for in range" + addition = "+" + subtraction = "-" + multiplication = "*" + division = "/" + smaller = "<" + smaller_equal = "<=" + bigger = ">" + bigger_equal = ">=" + not_equal = "!=" + pressed = "pressed" + clear = "clear" + define = "define" + call = "call" + returns = "return" + + +translatable_commands = { + Command.print: ["print"], + Command.ask: ["ask"], + Command.echo: ["echo"], + Command.turn: ["turn"], + Command.sleep: ["sleep"], + Command.color: ["color"], + Command.forward: ["forward"], + Command.add_to_list: ["add", "to_list"], + Command.remove_from_list: ["remove", "from"], + Command.list_access: ["at", "random"], + Command.in_list: ["in"], + Command.equality: ["is", "=", "=="], + Command.repeat: ["repeat", "times"], + Command.for_list: ["for", "in"], + Command.for_loop: ["in", "range", "to"], + Command.define: ["define"], + Command.call: ["call"], + Command.returns: ["return"], +} class HedyType: - any = 'any' - none = 'none' - string = 'string' - integer = 'integer' - list = 'list' - float = 'float' - boolean = 'boolean' - input = 'input' + any = "any" + none = "none" + string = "string" + integer = "integer" + list = "list" + float = "float" + boolean = "boolean" + input = "input" # Type promotion rules are used to implicitly convert one type to another, e.g. integer should be auto converted @@ -246,7 +248,7 @@ class HedyType: def promote_types(types, rules): - for (from_type, to_type) in rules: + for from_type, to_type in rules: if to_type in types: types = [to_type if t == from_type else t for t in types] return types @@ -254,60 +256,484 @@ def promote_types(types, rules): # Commands per Hedy level which are used to suggest the closest command when kids make a mistake commands_per_level = { - 1: ['print', 'ask', 'echo', 'turn', 'forward', 'color'], - 2: ['print', 'ask', 'is', 'turn', 'forward', 'color', 'sleep'], - 3: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from'], - 4: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'clear'], - 5: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'clear'], - 6: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'clear'], - 7: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'repeat', 'times', 'clear'], - 8: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'repeat', 'times', 'clear'], - 9: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'repeat', 'times', 'clear'], - 10: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'repeat', 'times', 'for', 'clear'], - 11: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'for', 'range', 'repeat', 'clear'], - 12: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'for', 'range', 'repeat', 'clear', 'define', 'call'], - 13: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'for', 'range', 'repeat', 'and', 'or', 'clear', 'define', 'call'], - 14: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'for', 'range', 'repeat', 'and', 'or', 'clear', 'define', 'call'], - 15: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'for', 'range', 'repeat', 'and', 'or', 'while', 'clear', 'define', 'call'], - 16: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'for', 'range', 'repeat', 'and', 'or', 'while', 'clear', 'define', 'call'], - 17: ['ask', 'is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'not in', 'if', 'else', 'pressed', 'button', 'for', 'range', 'repeat', 'and', 'or', 'while', 'elif', 'clear', 'define', 'call'], - 18: ['is', 'print', 'forward', 'turn', 'color', 'sleep', 'at', 'random', 'add', 'to', 'remove', 'from', 'in', 'if', 'not in', 'else', 'for', 'pressed', 'button', 'range', 'repeat', 'and', 'or', 'while', 'elif', 'input', 'clear', 'define', 'call'], + 1: ["print", "ask", "echo", "turn", "forward", "color"], + 2: ["print", "ask", "is", "turn", "forward", "color", "sleep"], + 3: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + ], + 4: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "clear", + ], + 5: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "clear", + ], + 6: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "clear", + ], + 7: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "repeat", + "times", + "clear", + ], + 8: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "repeat", + "times", + "clear", + ], + 9: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "repeat", + "times", + "clear", + ], + 10: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "repeat", + "times", + "for", + "clear", + ], + 11: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "for", + "range", + "repeat", + "clear", + ], + 12: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "for", + "range", + "repeat", + "clear", + "define", + "call", + ], + 13: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "for", + "range", + "repeat", + "and", + "or", + "clear", + "define", + "call", + ], + 14: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "for", + "range", + "repeat", + "and", + "or", + "clear", + "define", + "call", + ], + 15: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "for", + "range", + "repeat", + "and", + "or", + "while", + "clear", + "define", + "call", + ], + 16: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "for", + "range", + "repeat", + "and", + "or", + "while", + "clear", + "define", + "call", + ], + 17: [ + "ask", + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "not in", + "if", + "else", + "pressed", + "button", + "for", + "range", + "repeat", + "and", + "or", + "while", + "elif", + "clear", + "define", + "call", + ], + 18: [ + "is", + "print", + "forward", + "turn", + "color", + "sleep", + "at", + "random", + "add", + "to", + "remove", + "from", + "in", + "if", + "not in", + "else", + "for", + "pressed", + "button", + "range", + "repeat", + "and", + "or", + "while", + "elif", + "input", + "clear", + "define", + "call", + ], } -command_turn_literals = ['right', 'left'] -command_make_color = ['black', 'blue', 'brown', 'gray', 'green', 'orange', 'pink', 'purple', 'red', 'white', 'yellow'] +command_turn_literals = ["right", "left"] +command_make_color = [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", +] # Commands and their types per level (only partially filled!) commands_and_types_per_level = { Command.print: { 1: [HedyType.string, HedyType.integer, HedyType.input], 12: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float], - 16: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float, HedyType.list] + 16: [ + HedyType.string, + HedyType.integer, + HedyType.input, + HedyType.float, + HedyType.list, + ], }, Command.ask: { 1: [HedyType.string, HedyType.integer, HedyType.input], 12: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float], - 16: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float, HedyType.list] + 16: [ + HedyType.string, + HedyType.integer, + HedyType.input, + HedyType.float, + HedyType.list, + ], + }, + Command.turn: { + 1: command_turn_literals, + 2: [HedyType.integer, HedyType.input], + 12: [HedyType.integer, HedyType.input, HedyType.float], + }, + Command.color: { + 1: command_make_color, + 2: [command_make_color, HedyType.string, HedyType.input], + }, + Command.forward: { + 1: [HedyType.integer, HedyType.input], + 12: [HedyType.integer, HedyType.input, HedyType.float], }, - Command.turn: {1: command_turn_literals, - 2: [HedyType.integer, HedyType.input], - 12: [HedyType.integer, HedyType.input, HedyType.float] - }, - Command.color: {1: command_make_color, - 2: [command_make_color, HedyType.string, HedyType.input]}, - Command.forward: {1: [HedyType.integer, HedyType.input], - 12: [HedyType.integer, HedyType.input, HedyType.float] - }, Command.sleep: {1: [HedyType.integer, HedyType.input]}, Command.list_access: {1: [HedyType.list]}, Command.in_list: {1: [HedyType.list]}, Command.add_to_list: {1: [HedyType.list]}, Command.remove_from_list: {1: [HedyType.list]}, - Command.equality: {1: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float], - 14: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float, HedyType.list]}, + Command.equality: { + 1: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float], + 14: [ + HedyType.string, + HedyType.integer, + HedyType.input, + HedyType.float, + HedyType.list, + ], + }, Command.addition: { 6: [HedyType.integer, HedyType.input], - 12: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float] + 12: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float], }, Command.subtraction: { 1: [HedyType.integer, HedyType.input], @@ -328,33 +754,42 @@ def promote_types(types, rules): Command.smaller_equal: {14: [HedyType.integer, HedyType.float, HedyType.input]}, Command.bigger: {14: [HedyType.integer, HedyType.float, HedyType.input]}, Command.bigger_equal: {14: [HedyType.integer, HedyType.float, HedyType.input]}, - Command.not_equal: {14: [HedyType.integer, HedyType.float, HedyType.string, HedyType.input, HedyType.list]}, - Command.pressed: {5: [HedyType.string]} # TODO: maybe use a seperate type character in the future. + Command.not_equal: { + 14: [ + HedyType.integer, + HedyType.float, + HedyType.string, + HedyType.input, + HedyType.list, + ] + }, + Command.pressed: { + 5: [HedyType.string] + }, # TODO: maybe use a seperate type character in the future. } # we generate Python strings with ' always, so ' needs to be escaped but " works fine # \ also needs to be escaped because it eats the next character characters_that_need_escaping = ["\\", "'"] -character_skulpt_cannot_parse = re.compile('[^a-zA-Z0-9_]') +character_skulpt_cannot_parse = re.compile("[^a-zA-Z0-9_]") def get_list_keywords(commands, to_lang): - """ Returns a list with the local keywords of the argument 'commands' - """ + """Returns a list with the local keywords of the argument 'commands'""" translation_commands = [] dir = path.abspath(path.dirname(__file__)) path_keywords = dir + "/content/keywords" - to_yaml_filesname_with_path = path.join(path_keywords, to_lang + '.yaml') - en_yaml_filesname_with_path = path.join(path_keywords, 'en' + '.yaml') + to_yaml_filesname_with_path = path.join(path_keywords, to_lang + ".yaml") + en_yaml_filesname_with_path = path.join(path_keywords, "en" + ".yaml") - with open(en_yaml_filesname_with_path, 'r', encoding='utf-8') as stream: + with open(en_yaml_filesname_with_path, "r", encoding="utf-8") as stream: en_yaml_dict = yaml.safe_load(stream) try: - with open(to_yaml_filesname_with_path, 'r', encoding='utf-8') as stream: + with open(to_yaml_filesname_with_path, "r", encoding="utf-8") as stream: to_yaml_dict = yaml.safe_load(stream) for command in commands: try: @@ -370,12 +805,12 @@ def get_list_keywords(commands, to_lang): def get_suggestions_for_language(lang, level): if not local_keywords_enabled: - lang = 'en' + lang = "en" lang_commands = get_list_keywords(commands_per_level[level], lang) # if we allow multiple keyword languages: - en_commands = get_list_keywords(commands_per_level[level], 'en') + en_commands = get_list_keywords(commands_per_level[level], "en") en_lang_commands = list(set(en_commands + lang_commands)) return en_lang_commands @@ -392,14 +827,16 @@ def closest_command(invalid_command, known_commands, threshold=2): # returns None if the invalid command does not contain any known command. # returns 'keyword' if the invalid command is exactly a command (so shoudl not be suggested) - min_command = closest_command_with_min_distance(invalid_command, known_commands, threshold) + min_command = closest_command_with_min_distance( + invalid_command, known_commands, threshold + ) # Check if we are not returning the found command # In that case we have no suggestion # This is to prevent "print is not a command in Hedy level 3, did you mean print?" error message if min_command == invalid_command: - return 'keyword' + return "keyword" return min_command @@ -413,8 +850,13 @@ def closest_command_with_min_distance(invalid_command, commands, threshold): minimum_distance = 1000 closest_command = None for command in commands: - minimum_distance_for_command = calculate_minimum_distance(command, invalid_command) - if minimum_distance_for_command < minimum_distance and minimum_distance_for_command <= threshold: + minimum_distance_for_command = calculate_minimum_distance( + command, invalid_command + ) + if ( + minimum_distance_for_command < minimum_distance + and minimum_distance_for_command <= threshold + ): minimum_distance = minimum_distance_for_command closest_command = command @@ -432,7 +874,10 @@ def calculate_minimum_distance(s1, s2): if char1 == char2: new_distances.append(distances[index1]) else: - new_distances.append(1 + min((distances[index1], distances[index1 + 1], new_distances[-1]))) + new_distances.append( + 1 + + min((distances[index1], distances[index1 + 1], new_distances[-1])) + ) distances = new_distances return distances[-1] @@ -440,7 +885,7 @@ def calculate_minimum_distance(s1, s2): @dataclass class InvalidInfo: error_type: str - command: str = '' + command: str = "" arguments: list = field(default_factory=list) line: int = 0 column: int = 0 @@ -467,42 +912,42 @@ def __init__(self, data, children, meta, type_): class ExtractAST(Transformer): # simplifies the tree: f.e. flattens arguments of text, var and punctuation for further processing def text(self, meta, args): - return Tree('text', [' '.join([str(c) for c in args])], meta) + return Tree("text", [" ".join([str(c) for c in args])], meta) def INT(self, args): - return Tree('integer', [str(args)]) + return Tree("integer", [str(args)]) def NUMBER(self, args): - return Tree('number', [str(args)]) + return Tree("number", [str(args)]) def POSITIVE_NUMBER(self, args): - return Tree('number', [str(args)]) + return Tree("number", [str(args)]) def NEGATIVE_NUMBER(self, args): - return Tree('number', [str(args)]) + return Tree("number", [str(args)]) # level 2 def var(self, meta, args): - return Tree('var', [''.join([str(c) for c in args])], meta) + return Tree("var", ["".join([str(c) for c in args])], meta) def list_access(self, meta, args): # FH, may 2022 I don't fully understand why we remove INT here and just plemp # the number in the tree. should be improved but that requires rewriting the further processing code too (TODO) if isinstance(args[1], Tree): if "random" in args[1].data: - return Tree('list_access', [args[0], 'random'], meta) + return Tree("list_access", [args[0], "random"], meta) elif args[1].data == "var_access": - return Tree('list_access', [args[0], args[1].children[0]], meta) + return Tree("list_access", [args[0], args[1].children[0]], meta) else: # convert to latin int latin_int_index = str(int(args[1].children[0])) - return Tree('list_access', [args[0], latin_int_index], meta) + return Tree("list_access", [args[0], latin_int_index], meta) else: - return Tree('list_access', [args[0], args[1]], meta) + return Tree("list_access", [args[0], args[1]], meta) # level 5 def error_unsupported_number(self, meta, args): - return Tree('unsupported_number', [''.join([str(c) for c in args])], meta) + return Tree("unsupported_number", ["".join([str(c) for c in args])], meta) # This visitor collects all entries that should be part of the lookup table. It only stores the name of the entry @@ -510,6 +955,7 @@ def error_unsupported_number(self, meta, args): # of the entry. This preliminary traversal is needed to avoid issues with loops in which an iterator variable is # used in the inner commands which are visited before the iterator variable is added to the lookup. + class LookupEntryCollector(visitors.Visitor): def __init__(self, level): super().__init__() @@ -542,11 +988,11 @@ def assign_list(self, tree): def list_access(self, tree): list_name = escape_var(tree.children[0].children[0]) position_name = escape_var(tree.children[1]) - if position_name == 'random': - name = f'random.choice({list_name})' + if position_name == "random": + name = f"random.choice({list_name})" else: # We want list access to be 1-based instead of 0-based, hence the -1 - name = f'{list_name}[int({position_name})-1]' + name = f"{list_name}[int({position_name})-1]" self.add_to_lookup(name, tree, tree.meta.line, True) def change_list_item(self, tree): @@ -568,10 +1014,12 @@ def for_loop(self, tree): def define(self, tree): # add function name to lookup - self.add_to_lookup(str(tree.children[0].children[0]) + "()", tree, tree.meta.line) + self.add_to_lookup( + str(tree.children[0].children[0]) + "()", tree, tree.meta.line + ) # add arguments to lookup - if tree.children[1].data == 'arguments': + if tree.children[1].data == "arguments": for x in (c for c in tree.children[1].children if isinstance(c, Tree)): self.add_to_lookup(x.children[0], tree.children[1], tree.meta.line) @@ -579,8 +1027,10 @@ def call(self, tree): function_name = tree.children[0].children[0] args_str = "" if len(tree.children) > 1: - args_str = ", ".join(str(x.children[0]) if isinstance(x, Tree) else str(x) - for x in tree.children[1].children) + args_str = ", ".join( + str(x.children[0]) if isinstance(x, Tree) else str(x) + for x in tree.children[1].children + ) self.add_to_lookup(f"{function_name}({args_str})", tree, tree.meta.line) def add_to_lookup(self, name, tree, linenumber, skip_hashing=False): @@ -645,8 +1095,8 @@ def assign(self, tree): except hedy.exceptions.UndefinedVarException as ex: if self.level >= 12: raise hedy.exceptions.UnquotedAssignTextException( - text=ex.arguments['name'], - line_number=tree.meta.line) + text=ex.arguments["name"], line_number=tree.meta.line + ) else: raise @@ -657,24 +1107,30 @@ def assign_list(self, tree): return self.to_typed_tree(tree, HedyType.list) def list_access(self, tree): - self.validate_args_type_allowed(Command.list_access, tree.children[0], tree.meta) + self.validate_args_type_allowed( + Command.list_access, tree.children[0], tree.meta + ) list_name = escape_var(tree.children[0].children[0]) - if tree.children[1] == 'random': - name = f'random.choice({list_name})' + if tree.children[1] == "random": + name = f"random.choice({list_name})" else: # We want list access to be 1-based instead of 0-based, hence the -1 - name = f'{list_name}[int({tree.children[1]})-1]' + name = f"{list_name}[int({tree.children[1]})-1]" self.save_type_to_lookup(name, HedyType.any) return self.to_typed_tree(tree, HedyType.any) def add(self, tree): - self.validate_args_type_allowed(Command.add_to_list, tree.children[1], tree.meta) + self.validate_args_type_allowed( + Command.add_to_list, tree.children[1], tree.meta + ) return self.to_typed_tree(tree) def remove(self, tree): - self.validate_args_type_allowed(Command.remove_from_list, tree.children[1], tree.meta) + self.validate_args_type_allowed( + Command.remove_from_list, tree.children[1], tree.meta + ) return self.to_typed_tree(tree) def in_list_check(self, tree): @@ -683,7 +1139,14 @@ def in_list_check(self, tree): def equality_check(self, tree): if self.level < 12: - rules = [int_to_float, int_to_string, float_to_string, input_to_string, input_to_int, input_to_float] + rules = [ + int_to_float, + int_to_string, + float_to_string, + input_to_string, + input_to_int, + input_to_float, + ] else: rules = [int_to_float, input_to_string, input_to_int, input_to_float] self.validate_binary_command_args_type(Command.equality, tree, rules) @@ -706,7 +1169,9 @@ def for_loop(self, tree): command = Command.for_loop allowed_types = get_allowed_types(command, self.level) - start_type = self.check_type_allowed(command, allowed_types, tree.children[1], tree.meta) + start_type = self.check_type_allowed( + command, allowed_types, tree.children[1], tree.meta + ) self.check_type_allowed(command, allowed_types, tree.children[2], tree.meta) iterator = str(tree.children[0]) @@ -720,7 +1185,11 @@ def integer(self, tree): def text(self, tree): # under level 12 integers appear as text, so we parse them if self.level < 12: - type_ = HedyType.integer if ConvertToPython.is_int(tree.children[0]) else HedyType.string + type_ = ( + HedyType.integer + if ConvertToPython.is_int(tree.children[0]) + else HedyType.string + ) else: type_ = HedyType.string return self.to_typed_tree(tree, type_) @@ -744,7 +1213,7 @@ def number(self, tree): if ConvertToPython.is_float(number): return self.to_typed_tree(tree, HedyType.float) # We managed to parse a number that cannot be parsed by python - raise exceptions.ParseException(level=self.level, location='', found=number) + raise exceptions.ParseException(level=self.level, location="", found=number) def subtraction(self, tree): return self.to_sum_typed_tree(tree, Command.subtraction) @@ -760,7 +1229,9 @@ def division(self, tree): def to_sum_typed_tree(self, tree, command): rules = [int_to_float, input_to_int, input_to_float] - prom_left_type, prom_right_type = self.validate_binary_command_args_type(command, tree, rules) + prom_left_type, prom_right_type = self.validate_binary_command_args_type( + command, tree, rules + ) return TypedTree(tree.data, tree.children, tree.meta, prom_left_type) def smaller(self, tree): @@ -789,19 +1260,26 @@ def to_comparison_tree(self, command, tree): def validate_binary_command_args_type(self, command, tree, type_promotion_rules): allowed_types = get_allowed_types(command, self.level) - left_type = self.check_type_allowed(command, allowed_types, tree.children[0], tree.meta) - right_type = self.check_type_allowed(command, allowed_types, tree.children[1], tree.meta) + left_type = self.check_type_allowed( + command, allowed_types, tree.children[0], tree.meta + ) + right_type = self.check_type_allowed( + command, allowed_types, tree.children[1], tree.meta + ) if self.ignore_type(left_type) or self.ignore_type(right_type): return HedyType.any, HedyType.any - prom_left_type, prom_right_type = promote_types([left_type, right_type], type_promotion_rules) + prom_left_type, prom_right_type = promote_types( + [left_type, right_type], type_promotion_rules + ) if prom_left_type != prom_right_type: left_arg = tree.children[0].children[0] right_arg = tree.children[1].children[0] raise hedy.exceptions.InvalidTypeCombinationException( - command, left_arg, right_arg, left_type, right_type, tree.meta.line) + command, left_arg, right_arg, left_type, right_type, tree.meta.line + ) return prom_left_type, prom_right_type def validate_args_type_allowed(self, command, children, meta): @@ -825,25 +1303,35 @@ def check_type_allowed(self, command, allowed_types, tree, meta=None): meta.line, meta.end_line, meta.column - 1, - meta.end_column - 2) + meta.end_column - 2, + ) result = {k: v for k, v in result.items()} - command = ' '.join([v.strip() for v in result.values() if v is not None]) - raise exceptions.InvalidArgumentTypeException(command=command, invalid_type=arg_type, - invalid_argument=variable, allowed_types=allowed_types, line_number=meta.line) + command = " ".join( + [v.strip() for v in result.values() if v is not None] + ) + raise exceptions.InvalidArgumentTypeException( + command=command, + invalid_type=arg_type, + invalid_argument=variable, + allowed_types=allowed_types, + line_number=meta.line, + ) return arg_type def get_type(self, tree): # The rule var_access is used in the grammars definitions only in places where a variable needs to be accessed. # So, if it cannot be found in the lookup table, then it is an undefined variable for sure. - if tree.data == 'var_access': + if tree.data == "var_access": var_name = tree.children[0] in_lookup, type_in_lookup = self.try_get_type_from_lookup(var_name) if in_lookup: return type_in_lookup else: - raise hedy.exceptions.UndefinedVarException(name=var_name, line_number=tree.meta.line) + raise hedy.exceptions.UndefinedVarException( + name=var_name, line_number=tree.meta.line + ) - if tree.data == 'var_access_print': + if tree.data == "var_access_print": var_name = tree.children[0] in_lookup, type_in_lookup = self.try_get_type_from_lookup(var_name) if in_lookup: @@ -856,17 +1344,28 @@ def get_type(self, tree): # TODO: Can be removed since fall back handles that now if len(self.lookup) == 0: raise hedy.exceptions.UnquotedTextException( - level=self.level, unquotedtext=var_name, line_number=tree.meta.line) + level=self.level, + unquotedtext=var_name, + line_number=tree.meta.line, + ) else: # TODO: decide when this runs for a while whether this distance small enough! minimum_distance_allowed = 4 for var_in_lookup in self.lookup: - if calculate_minimum_distance(var_in_lookup.name, var_name) <= minimum_distance_allowed: - raise hedy.exceptions.UndefinedVarException(name=var_name, line_number=tree.meta.line) + if ( + calculate_minimum_distance(var_in_lookup.name, var_name) + <= minimum_distance_allowed + ): + raise hedy.exceptions.UndefinedVarException( + name=var_name, line_number=tree.meta.line + ) # nothing found? fall back to UnquotedTextException raise hedy.exceptions.UnquotedTextException( - level=self.level, unquotedtext=var_name, line_number=tree.meta.line) + level=self.level, + unquotedtext=var_name, + line_number=tree.meta.line, + ) # TypedTree with type 'None' and 'string' could be in the lookup because of the grammar definitions # If the tree has more than 1 child, then it is not a leaf node, so do not search in the lookup @@ -898,13 +1397,18 @@ def try_get_type_from_lookup(self, name): if matches: match = matches[0] if not match.type_: - if match.currently_inferring: # there is a cyclic var reference, e.g. b = b + 1 + if ( + match.currently_inferring + ): # there is a cyclic var reference, e.g. b = b + 1 raise exceptions.CyclicVariableDefinitionException( - variable=match.name, line_number=match.tree.meta.line) + variable=match.name, line_number=match.tree.meta.line + ) else: match.currently_inferring = True try: - TypeValidator(self.lookup, self.level, self.lang, self.input_string).transform(match.tree) + TypeValidator( + self.lookup, self.level, self.lang, self.input_string + ).transform(match.tree) except VisitError as ex: raise ex.orig_exc match.currently_inferring = False @@ -928,8 +1432,8 @@ def flatten_list_of_lists_to_list(args): flat_list = [] for element in args: if isinstance( - element, - str): # str needs a special case before list because a str is also a list and we don't want to split all letters out + element, str + ): # str needs a special case before list because a str is also a list and we don't want to split all letters out flat_list.append(element) elif isinstance(element, list): flat_list += flatten_list_of_lists_to_list(element) @@ -940,7 +1444,9 @@ def flatten_list_of_lists_to_list(args): def are_all_arguments_true(args): bool_arguments = [x[0] for x in args] - arguments_of_false_nodes = flatten_list_of_lists_to_list([x[1] for x in args if not x[0]]) + arguments_of_false_nodes = flatten_list_of_lists_to_list( + [x[1] for x in args if not x[0]] + ) return all(bool_arguments), arguments_of_false_nodes @@ -963,24 +1469,26 @@ def program(self, meta, args): # leafs are treated differently, they are True + their arguments flattened def var(self, meta, args): - return True, ''.join([str(c) for c in args]), meta + return True, "".join([str(c) for c in args]), meta def random(self, meta, args): - return True, 'random', meta + return True, "random", meta def number(self, meta, args): - return True, ''.join([c for c in args]), meta + return True, "".join([c for c in args]), meta def NEGATIVE_NUMBER(self, args): - return True, ''.join([c for c in args]), None + return True, "".join([c for c in args]), None def text(self, meta, args): - return all(args), ''.join([c for c in args]), meta + return all(args), "".join([c for c in args]), meta class UsesTurtle(Transformer): def __default__(self, args, children, meta): - if len(children) == 0: # no children? you are a leaf that is not Turn or Forward, so you are no Turtle command + if ( + len(children) == 0 + ): # no children? you are a leaf that is not Turn or Forward, so you are no Turtle command return False else: return any(isinstance(c, bool) and c is True for c in children) @@ -1013,7 +1521,7 @@ def NEGATIVE_NUMBER(self, args): class UsesPyGame(Transformer): - command_prefix = (f"""\ + command_prefix = f"""\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1021,10 +1529,12 @@ class UsesPyGame(Transformer): if event.type == pygame.QUIT: pygame_end = True pygame.quit() - break""") + break""" def __default__(self, args, children, meta): - if len(children) == 0: # no children? you are a leaf that is not Pressed, so you are no PyGame command + if ( + len(children) == 0 + ): # no children? you are a leaf that is not Pressed, so you are no PyGame command return False else: return any(isinstance(c, bool) and c is True for c in children) @@ -1047,30 +1557,30 @@ def translate_keyword(self, keyword): # some keywords have names that are not a valid name for a command # that's why we call them differently in the grammar # we have to translate them to the regular names here for further communciation - if keyword in ['assign', 'assign_list']: - return 'is' - if keyword == 'ifelse': - return 'else' - if keyword == 'ifs': - return 'if' - if keyword == 'elifs': - return 'elif' - if keyword == 'for_loop': - return 'for' - if keyword == 'for_list': - return 'for' - if keyword == 'or_condition': - return 'or' - if keyword == 'and_condition': - return 'and' - if keyword == 'while_loop': - return 'while' - if keyword == 'in_list_check': - return 'in' - if keyword == 'input_empty_brackets': - return 'input' - if keyword == 'print_empty_brackets': - return 'print' + if keyword in ["assign", "assign_list"]: + return "is" + if keyword == "ifelse": + return "else" + if keyword == "ifs": + return "if" + if keyword == "elifs": + return "elif" + if keyword == "for_loop": + return "for" + if keyword == "for_list": + return "for" + if keyword == "or_condition": + return "or" + if keyword == "and_condition": + return "and" + if keyword == "while_loop": + return "while" + if keyword == "in_list_check": + return "in" + if keyword == "input_empty_brackets": + return "input" + if keyword == "print_empty_brackets": + return "print" return str(keyword) def __default__(self, args, children, meta): @@ -1078,11 +1588,14 @@ def __default__(self, args, children, meta): production_rule_name = self.translate_keyword(args) leaves = flatten_list_of_lists_to_list(children) # for the achievements we want to be able to also detct which operators were used by a kid - operators = ['addition', 'subtraction', 'multiplication', 'division'] - - if production_rule_name in commands_per_level[self.level] or production_rule_name in operators: - if production_rule_name == 'else': # use of else also has an if - return ['if', 'else'] + leaves + operators = ["addition", "subtraction", "multiplication", "division"] + + if ( + production_rule_name in commands_per_level[self.level] + or production_rule_name in operators + ): + if production_rule_name == "else": # use of else also has an if + return ["if", "else"] + leaves return [production_rule_name] + leaves else: return leaves # 'pop up' the children @@ -1113,7 +1626,7 @@ def text(self, args): return [] -def all_commands(input_string, level, lang='en'): +def all_commands(input_string, level, lang="en"): input_string = process_input_string(input_string, level, lang) program_root = parse_input(input_string, level, lang) @@ -1127,7 +1640,7 @@ def __init__(self, level): def __default__(self, args, children, meta): leaves = flatten_list_of_lists_to_list(children) - if args == 'print': + if args == "print": return children else: return leaves # 'pop up' the children @@ -1152,10 +1665,10 @@ def NEGATIVE_NUMBER(self, args): return [] def text(self, args): - return ''.join(args) + return "".join(args) -def all_print_arguments(input_string, level, lang='en'): +def all_print_arguments(input_string, level, lang="en"): input_string = process_input_string(input_string, level, lang) program_root = parse_input(input_string, level, lang) @@ -1170,62 +1683,125 @@ class IsValid(Filter): def error_invalid_space(self, meta, args): # return space to indicate that line starts in a space - return False, InvalidInfo(" ", line=args[0][2].line, column=args[0][2].column), meta + return ( + False, + InvalidInfo(" ", line=args[0][2].line, column=args[0][2].column), + meta, + ) def error_print_nq(self, meta, args): words = [str(x[1]) for x in args] # second half of the list is the word - text = ' '.join(words) - return False, InvalidInfo("print without quotes", arguments=[ - text], line=meta.line, column=meta.column), meta + text = " ".join(words) + return ( + False, + InvalidInfo( + "print without quotes", + arguments=[text], + line=meta.line, + column=meta.column, + ), + meta, + ) def error_invalid(self, meta, args): # TODO: this will not work for misspelling 'at', needs to be improved! - error = InvalidInfo('invalid command', command=args[0][1], arguments=[ - [a[1] for a in args[1:]]], line=meta.line, column=meta.column) + error = InvalidInfo( + "invalid command", + command=args[0][1], + arguments=[[a[1] for a in args[1:]]], + line=meta.line, + column=meta.column, + ) return False, error, meta def error_unsupported_number(self, meta, args): - error = InvalidInfo('unsupported number', arguments=[str(args[0])], line=meta.line, column=meta.column) + error = InvalidInfo( + "unsupported number", + arguments=[str(args[0])], + line=meta.line, + column=meta.column, + ) return False, error, meta def error_condition(self, meta, args): - error = InvalidInfo('invalid condition', arguments=[str(args[0])], line=meta.line, column=meta.column) + error = InvalidInfo( + "invalid condition", + arguments=[str(args[0])], + line=meta.line, + column=meta.column, + ) return False, error, meta def error_repeat_no_command(self, meta, args): - error = InvalidInfo('invalid repeat', arguments=[str(args[0])], line=meta.line, column=meta.column) + error = InvalidInfo( + "invalid repeat", + arguments=[str(args[0])], + line=meta.line, + column=meta.column, + ) return False, error, meta def error_repeat_no_print(self, meta, args): - error = InvalidInfo('repeat missing print', arguments=[str(args[0])], line=meta.line, column=meta.column) + error = InvalidInfo( + "repeat missing print", + arguments=[str(args[0])], + line=meta.line, + column=meta.column, + ) return False, error, meta def error_repeat_no_times(self, meta, args): - error = InvalidInfo('repeat missing times', arguments=[str(args[0])], line=meta.line, column=meta.column) + error = InvalidInfo( + "repeat missing times", + arguments=[str(args[0])], + line=meta.line, + column=meta.column, + ) return False, error, meta def error_text_no_print(self, meta, args): - error = InvalidInfo('lonely text', arguments=[str(args[0])], line=meta.line, column=meta.column) + error = InvalidInfo( + "lonely text", arguments=[str(args[0])], line=meta.line, column=meta.column + ) return False, error, meta def error_list_access_at(self, meta, args): - error = InvalidInfo('invalid at keyword', arguments=[str(args[0])], line=meta.line, column=meta.column) + error = InvalidInfo( + "invalid at keyword", + arguments=[str(args[0])], + line=meta.line, + column=meta.column, + ) return False, error, meta + # other rules are inherited from Filter # flat if no longer allowed in level 8 and up def error_ifelse(self, meta, args): - error = InvalidInfo('flat if', arguments=[str(args[0])], line=meta.line, column=meta.column) + error = InvalidInfo( + "flat if", arguments=[str(args[0])], line=meta.line, column=meta.column + ) return False, error, meta def error_ifpressed_missing_else(self, meta, args): - error = InvalidInfo('ifpressed missing else', arguments=[str(args[0])], line=meta.line, column=meta.column) + error = InvalidInfo( + "ifpressed missing else", + arguments=[str(args[0])], + line=meta.line, + column=meta.column, + ) return False, error, meta def error_nested_define(self, meta, args): - error = InvalidInfo('nested function', arguments=[str(args[0])], line=meta.line, column=meta.column) + error = InvalidInfo( + "nested function", + arguments=[str(args[0])], + line=meta.line, + column=meta.column, + ) return False, error, meta + # other rules are inherited from Filter @@ -1233,19 +1809,23 @@ def error_nested_define(self, meta, args): def valid_echo(ast): commands = ast.children command_names = [x.children[0].data for x in commands] - no_echo = 'echo' not in command_names + no_echo = "echo" not in command_names # no echo is always ok! # otherwise, both have to be in the list and echo should come after - return no_echo or ('echo' in command_names and 'ask' in command_names) and command_names.index( - 'echo') > command_names.index('ask') + return ( + no_echo + or ("echo" in command_names and "ask" in command_names) + and command_names.index("echo") > command_names.index("ask") + ) @v_args(meta=True) class IsComplete(Filter): def __init__(self, level): self.level = level + # print, ask and echo can miss arguments and then are not complete # used to generate more informative error messages # tree is transformed to a node of [True] or [False, args, line_number] @@ -1253,27 +1833,29 @@ def __init__(self, level): def ask(self, meta, args): # in level 1 ask without arguments means args == [] # in level 2 and up, ask without arguments is a list of 1, namely the var name - incomplete = (args == [] and self.level == 1) or (len(args) == 1 and self.level >= 2) + incomplete = (args == [] and self.level == 1) or ( + len(args) == 1 and self.level >= 2 + ) if meta is not None: - return not incomplete, ('ask', meta.line) + return not incomplete, ("ask", meta.line) else: - return not incomplete, ('ask', 1) + return not incomplete, ("ask", 1) def print(self, meta, args): - return args != [], ('print', meta.line) + return args != [], ("print", meta.line) def input(self, meta, args): - return len(args) > 1, ('input', meta.line) + return len(args) > 1, ("input", meta.line) def length(self, meta, args): - return args != [], ('len', meta.line) + return args != [], ("len", meta.line) def error_print_nq(self, meta, args): - return args != [], ('print level 2', meta.line) + return args != [], ("print level 2", meta.line) def echo(self, meta, args): # echo may miss an argument - return True, ('echo', meta.line) + return True, ("echo", meta.line) # other rules are inherited from Filter @@ -1281,13 +1863,17 @@ def echo(self, meta, args): def process_characters_needing_escape(value): # defines what happens if a kids uses ' or \ in in a string for c in characters_that_need_escaping: - value = value.replace(c, f'\\{c}') + value = value.replace(c, f"\\{c}") return value def get_allowed_types(command, level): # get only the allowed types of the command for all levels before the requested level - allowed = [values for key, values in commands_and_types_per_level[command].items() if key <= level] + allowed = [ + values + for key, values in commands_and_types_per_level[command].items() + if key <= level + ] # use the allowed types of the highest level available return allowed[-1] if allowed else [] @@ -1298,6 +1884,7 @@ def decorator(c): TRANSPILER_LOOKUP[level] = c c.level = level return c + return decorator @@ -1311,21 +1898,31 @@ def __init__(self, lookup, numerals_language="Latin"): # is no check on whether the var is defined def is_variable(self, variable_name, access_line_number=100): all_names = [a.name for a in self.lookup] - all_names_before_access_line = [a.name for a in self.lookup if a.linenumber <= access_line_number] - - if variable_name in all_names and variable_name not in all_names_before_access_line: + all_names_before_access_line = [ + a.name for a in self.lookup if a.linenumber <= access_line_number + ] + + if ( + variable_name in all_names + and variable_name not in all_names_before_access_line + ): # referenced before assignment! - definition_line_number = [a.linenumber for a in self.lookup if a.name == variable_name][0] + definition_line_number = [ + a.linenumber for a in self.lookup if a.name == variable_name + ][0] raise hedy.exceptions.AccessBeforeAssign( name=variable_name, access_line_number=access_line_number, - definition_line_number=definition_line_number) + definition_line_number=definition_line_number, + ) is_function = False if isinstance(variable_name, str): - pattern = r'^([a-zA-Z_][a-zA-Z0-9_]*)\(' + pattern = r"^([a-zA-Z_][a-zA-Z0-9_]*)\(" match = re.match(pattern, variable_name) - is_function = match and [a.name for a in self.lookup if match.group(1) + "()" == a.name] + is_function = match and [ + a.name for a in self.lookup if match.group(1) + "()" == a.name + ] return escape_var(variable_name) in all_names_before_access_line or is_function @@ -1353,7 +1950,7 @@ def process_variable_for_comparisons(self, name): return f"{name}" def make_f_string(self, args): - argument_string = '' + argument_string = "" for argument in args: if self.is_variable(argument): # variables are placed in {} in the f string @@ -1362,13 +1959,13 @@ def make_f_string(self, args): # strings are written regularly # however we no longer need the enclosing quotes in the f-string # the quotes are only left on the argument to check if they are there. - argument_string += argument.replace("'", '') + argument_string += argument.replace("'", "") return f"print(f'{argument_string}')" def get_fresh_var(self, name): while self.is_variable(name): - name = '_' + name + name = "_" + name return name def check_var_usage(self, args, var_access_linenumber=100): @@ -1376,14 +1973,20 @@ def check_var_usage(self, args, var_access_linenumber=100): # we can proceed if all arguments are either quoted OR all variables def is_var_candidate(arg) -> bool: - return not isinstance(arg, Tree) and \ - not ConvertToPython.is_int(arg) and \ - not ConvertToPython.is_float(arg) + return ( + not isinstance(arg, Tree) + and not ConvertToPython.is_int(arg) + and not ConvertToPython.is_float(arg) + ) - args_to_process = [a for a in args if is_var_candidate(a)] # we do not check trees (calcs) they are always ok + args_to_process = [ + a for a in args if is_var_candidate(a) + ] # we do not check trees (calcs) they are always ok unquoted_args = [a for a in args_to_process if not ConvertToPython.is_quoted(a)] - unquoted_in_lookup = [self.is_variable(a, var_access_linenumber) for a in unquoted_args] + unquoted_in_lookup = [ + self.is_variable(a, var_access_linenumber) for a in unquoted_args + ] if unquoted_in_lookup == [] or all(unquoted_in_lookup): # all good? return for further processing @@ -1392,13 +1995,15 @@ def is_var_candidate(arg) -> bool: # TODO: check whether this is really never raised?? # return first name with issue first_unquoted_var = unquoted_args[0] - raise exceptions.UndefinedVarException(name=first_unquoted_var, line_number=var_access_linenumber) + raise exceptions.UndefinedVarException( + name=first_unquoted_var, line_number=var_access_linenumber + ) # static methods @staticmethod def is_quoted(s): - opening_quotes = ['‘', "'", '"', "“", "«"] - closing_quotes = ['’', "'", '"', "”", "»"] + opening_quotes = ["‘", "'", '"', "“", "«"] + closing_quotes = ["’", "'", '"', "”", "»"] return len(s) > 1 and (s[0] in opening_quotes and s[-1] in closing_quotes) @staticmethod @@ -1419,40 +2024,39 @@ def is_float(n): @staticmethod def is_random(s): - return 'random.choice' in s + return "random.choice" in s @staticmethod def is_list(s): - return '[' in s and ']' in s + return "[" in s and "]" in s @staticmethod def indent(s, spaces_amount=2): - lines = s.split('\n') - return '\n'.join([' ' * spaces_amount + line for line in lines]) + lines = s.split("\n") + return "\n".join([" " * spaces_amount + line for line in lines]) @v_args(meta=True) @hedy_transpiler(level=1) @source_map_transformer(source_map) class ConvertToPython_1(ConvertToPython): - def __init__(self, lookup, numerals_language): self.numerals_language = numerals_language self.lookup = lookup __class__.level = 1 def program(self, meta, args): - return '\n'.join([str(c) for c in args]) + return "\n".join([str(c) for c in args]) def command(self, meta, args): return args[0] def text(self, meta, args): - return ''.join([str(c) for c in args]) + return "".join([str(c) for c in args]) def integer(self, meta, args): # remove whitespaces - return str(int(args[0].replace(' ', ''))) + return str(int(args[0].replace(" ", ""))) def number(self, meta, args): return str(int(args[0])) @@ -1480,11 +2084,11 @@ def comment(self, meta, args): return f"#{''.join(args)}" def empty_line(self, meta, args): - return '' + return "" def forward(self, meta, args): if len(args) == 0: - return sleep_after('t.forward(50)', False) + return sleep_after("t.forward(50)", False) return self.make_forward(int(args[0])) def color(self, meta, args): @@ -1496,83 +2100,105 @@ def color(self, meta, args): return f"t.pencolor('{arg}')" else: # the TypeValidator should protect against reaching this line: - raise exceptions.InvalidArgumentTypeException(command=Command.color, invalid_type='', invalid_argument=arg, - allowed_types=get_allowed_types(Command.color, self.level), line_number=meta.line) + raise exceptions.InvalidArgumentTypeException( + command=Command.color, + invalid_type="", + invalid_argument=arg, + allowed_types=get_allowed_types(Command.color, self.level), + line_number=meta.line, + ) def turn(self, meta, args): if len(args) == 0: return "t.right(90)" # no arguments defaults to a right turn arg = args[0].data - if arg == 'left': + if arg == "left": return "t.left(90)" - elif arg == 'right': + elif arg == "right": return "t.right(90)" else: # the TypeValidator should protect against reaching this line: - raise exceptions.InvalidArgumentTypeException(command=Command.turn, invalid_type='', invalid_argument=arg, - allowed_types=get_allowed_types(Command.turn, self.level), line_number=meta.line) + raise exceptions.InvalidArgumentTypeException( + command=Command.turn, + invalid_type="", + invalid_argument=arg, + allowed_types=get_allowed_types(Command.turn, self.level), + line_number=meta.line, + ) def make_turn(self, parameter): - return self.make_turtle_command(parameter, Command.turn, 'right', False, 'int') + return self.make_turtle_command(parameter, Command.turn, "right", False, "int") def make_forward(self, parameter): - return self.make_turtle_command(parameter, Command.forward, 'forward', True, 'int') + return self.make_turtle_command( + parameter, Command.forward, "forward", True, "int" + ) def make_color(self, parameter): - return self.make_turtle_color_command(parameter, Command.color, 'pencolor') + return self.make_turtle_color_command(parameter, Command.color, "pencolor") def make_turtle_command(self, parameter, command, command_text, add_sleep, type): - exception = '' + exception = "" if isinstance(parameter, str): exception = self.make_catch_exception([parameter]) - variable = self.get_fresh_var('__trtl') - transpiled = exception + textwrap.dedent(f"""\ + variable = self.get_fresh_var("__trtl") + transpiled = exception + textwrap.dedent( + f"""\ {variable} = {parameter} try: {variable} = {type}({variable}) except ValueError: raise Exception(f'While running your program the command {style_command(command)} received the value {style_command('{' + variable + '}')} which is not allowed. Try changing the value to a number.') - t.{command_text}(min(600, {variable}) if {variable} > 0 else max(-600, {variable}))""") + t.{command_text}(min(600, {variable}) if {variable} > 0 else max(-600, {variable}))""" + ) if add_sleep: return sleep_after(transpiled, False) return transpiled def make_turtle_color_command(self, parameter, command, command_text): - variable = self.get_fresh_var('__trtl') - return textwrap.dedent(f"""\ + variable = self.get_fresh_var("__trtl") + return textwrap.dedent( + f"""\ {variable} = f'{parameter}' if {variable} not in {command_make_color}: raise Exception(f'While running your program the command {style_command(command)} received the value {style_command('{' + variable + '}')} which is not allowed. Try using another color.') - t.{command_text}({variable})""") + t.{command_text}({variable})""" + ) def make_catch_exception(self, args): lists_names = [] list_args = [] - var_regex = r"[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}_]+|[\p{Mn}\p{Mc}\p{Nd}\p{Pc}·]+" + var_regex = ( + r"[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}_]+|[\p{Mn}\p{Mc}\p{Nd}\p{Pc}·]+" + ) # List usage comes in indexation and random choice - list_regex = fr"(({var_regex})+\[int\(({var_regex})\)-1\])|(random\.choice\(({var_regex})\))" + list_regex = rf"(({var_regex})+\[int\(({var_regex})\)-1\])|(random\.choice\(({var_regex})\))" for arg in args: # Expressions come inside a Tree object, so unpack them if isinstance(arg, Tree): arg = arg.children[0] for group in regex.findall(list_regex, arg): - if group[0] != '': + if group[0] != "": list_args.append(group[0]) lists_names.append(group[1]) else: list_args.append(group[3]) lists_names.append(group[4]) code = "" - exception_text_template = gettext('catch_index_exception') + exception_text_template = gettext("catch_index_exception") for i, list_name in enumerate(lists_names): - exception_text = exception_text_template.replace('{list_name}', style_command(list_name)) - code += textwrap.dedent(f"""\ + exception_text = exception_text_template.replace( + "{list_name}", style_command(list_name) + ) + code += textwrap.dedent( + f"""\ try: {list_args[i]} except IndexError: raise Exception('{exception_text}') - """) + """ + ) return code @@ -1580,16 +2206,15 @@ def make_catch_exception(self, args): @hedy_transpiler(level=2) @source_map_transformer(source_map) class ConvertToPython_2(ConvertToPython_1): - def error_ask_dep_2(self, meta, args): # ask is no longer usable this way, raise! # ask_needs_var is an entry in lang.yaml in texts where we can add extra info on this error - raise hedy.exceptions.WrongLevelException(1, 'ask', "ask_needs_var", meta.line) + raise hedy.exceptions.WrongLevelException(1, "ask", "ask_needs_var", meta.line) def error_echo_dep_2(self, meta, args): # echo is no longer usable this way, raise! # ask_needs_var is an entry in lang.yaml in texts where we can add extra info on this error - raise hedy.exceptions.WrongLevelException(1, 'echo', "echo_out", meta.line) + raise hedy.exceptions.WrongLevelException(1, "echo", "echo_out", meta.line) def color(self, meta, args): if len(args) == 0: @@ -1637,20 +2262,28 @@ def print(self, meta, args): else: # this regex splits words from non-letter characters, such that name! becomes [name, !] res = regex.findall( - r"[·\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}]+|[^·\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}]+", a) - args_new.append(''.join([self.process_variable_for_fstring(x, meta.line) for x in res])) + r"[·\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}]+|[^·\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}]+", + a, + ) + args_new.append( + "".join( + [self.process_variable_for_fstring(x, meta.line) for x in res] + ) + ) exception = self.make_catch_exception(args) - argument_string = ' '.join(args_new) + argument_string = " ".join(args_new) return exception + f"print(f'{argument_string}')" def ask(self, meta, args): var = args[0] - all_parameters = ["'" + process_characters_needing_escape(a) + "'" for a in args[1:]] - return f'{var} = input(' + '+'.join(all_parameters) + ")" + all_parameters = [ + "'" + process_characters_needing_escape(a) + "'" for a in args[1:] + ] + return f"{var} = input(" + "+".join(all_parameters) + ")" def forward(self, meta, args): if len(args) == 0: - return sleep_after('t.forward(50)', False) + return sleep_after("t.forward(50)", False) if ConvertToPython.is_int(args[0]): parameter = int(args[0]) @@ -1682,10 +2315,12 @@ def sleep(self, meta, args): value = f'"{args[0]}"' if self.is_int(args[0]) else args[0] exceptions = self.make_catch_exception(args) try_prefix = "try:\n" + textwrap.indent(exceptions, " ") - code = try_prefix + textwrap.dedent(f"""\ + code = try_prefix + textwrap.dedent( + f"""\ time.sleep(int({value})) except ValueError: - raise Exception(f'While running your program the command {style_command(Command.sleep)} received the value {style_command('{' + value + '}')} which is not allowed. Try changing the value to a number.')""") + raise Exception(f'While running your program the command {style_command(Command.sleep)} received the value {style_command('{' + value + '}')} which is not allowed. Try changing the value to a number.')""" + ) return code @@ -1702,21 +2337,25 @@ def list_access(self, meta, args): args = [escape_var(a) for a in args] # check the arguments (except when they are random or numbers, that is not quoted nor a var but is allowed) - self.check_var_usage([a for a in args if a != 'random' and not a.isnumeric()], meta.line) + self.check_var_usage( + [a for a in args if a != "random" and not a.isnumeric()], meta.line + ) - if args[1] == 'random': - return 'random.choice(' + args[0] + ')' + if args[1] == "random": + return "random.choice(" + args[0] + ")" else: - return args[0] + '[int(' + args[1] + ')-1]' + return args[0] + "[int(" + args[1] + ")-1]" def process_argument(self, meta, arg): # only call process_variable if arg is a string, else keep as is (ie. # don't change 5 into '5', my_list[1] into 'my_list[1]') if arg.isnumeric() and isinstance(arg, int): # is int/float return arg - elif (self.is_list(arg)): # is list indexing - before_index, after_index = arg.split(']', 1) - return before_index + '-1' + ']' + after_index # account for 1-based indexing + elif self.is_list(arg): # is list indexing + before_index, after_index = arg.split("]", 1) + return ( + before_index + "-1" + "]" + after_index + ) # account for 1-based indexing else: return self.process_variable(arg, meta.line) @@ -1728,29 +2367,34 @@ def add(self, meta, args): def remove(self, meta, args): value = self.process_argument(meta, args[0]) list_var = args[1] - return textwrap.dedent(f"""\ + return textwrap.dedent( + f"""\ try: {list_var}.remove({value}) except: - pass""") + pass""" + ) @v_args(meta=True) @hedy_transpiler(level=4) @source_map_transformer(source_map) class ConvertToPython_4(ConvertToPython_3): - def process_variable_for_fstring(self, name): if self.is_variable(name): if self.numerals_language == "Latin": converted = escape_var(name) else: - converted = f'convert_numerals("{self.numerals_language}",{escape_var(name)})' + converted = ( + f'convert_numerals("{self.numerals_language}",{escape_var(name)})' + ) return "{" + converted + "}" else: if self.is_quoted(name): name = name[1:-1] - return name.replace("'", "\\'") # at level 4 backslashes are escaped in preprocessing, so we escape only ' + return name.replace( + "'", "\\'" + ) # at level 4 backslashes are escaped in preprocessing, so we escape only ' def var_access(self, meta, args): name = args[0] @@ -1762,7 +2406,7 @@ def var_access_print(self, meta, args): def print_ask_args(self, meta, args): args = self.check_var_usage(args, meta.line) - result = '' + result = "" for argument in args: argument = self.process_variable_for_fstring(argument) result += argument @@ -1811,16 +2455,16 @@ def ifelse(self, meta, args): {ConvertToPython.indent(args[2])}""" def condition(self, meta, args): - return ' and '.join(args) + return " and ".join(args) def condition_spaces(self, meta, args): arg0 = self.process_variable(args[0], meta.line) - arg1 = self.process_variable(' '.join(args[1:])) + arg1 = self.process_variable(" ".join(args[1:])) return f"{arg0} == {arg1}" def equality_check(self, meta, args): arg0 = self.process_variable(args[0], meta.line) - arg1 = ' '.join([self.process_variable(a) for a in args[1:]]) + arg1 = " ".join([self.process_variable(a) for a in args[1:]]) return f"{arg0} == {arg1}" # TODO, FH 2021: zelfde change moet ik ook nog ff maken voor equal. check in hogere levels @@ -1858,37 +2502,45 @@ def make_ifpressed_command(self, command, button=False, add_command_prefix=True) def ifpressed_else(self, meta, args): var_or_button = args[0] if self.is_variable(var_or_button): - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.key == {var_or_button}: {ConvertToPython.indent(args[1])} break else: {ConvertToPython.indent(args[2])} - break""", button=False) + break""", + button=False, + ) elif len(var_or_button) > 1: button_name = self.process_variable(args[0], meta.line) - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.key == {button_name}: {ConvertToPython.indent(args[1])} break else: {ConvertToPython.indent(args[2])} - break""", button=True) + break""", + button=True, + ) else: - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.unicode == '{args[0]}': {ConvertToPython.indent(args[1])} break else: {ConvertToPython.indent(args[2])} - break""", button=False) + break""", + button=False, + ) @v_args(meta=True) @hedy_transpiler(level=6) @source_map_transformer(source_map) class ConvertToPython_6(ConvertToPython_5): - def print_ask_args(self, meta, args): # we only check non-Tree (= non calculation) arguments self.check_var_usage(args, meta.line) @@ -1900,16 +2552,18 @@ def print_ask_args(self, meta, args): if self.numerals_language == "Latin": args_new.append("{" + a.children[0] + "}") else: - converted = f'convert_numerals("{self.numerals_language}",{a.children[0]})' + converted = ( + f'convert_numerals("{self.numerals_language}",{a.children[0]})' + ) args_new.append("{" + converted + "}") else: args_new.append(self.process_variable_for_fstring(a)) - return ''.join(args_new) + return "".join(args_new) def equality_check(self, meta, args): arg0 = self.process_variable(args[0], meta.line) - remaining_text = ' '.join(args[1:]) + remaining_text = " ".join(args[1:]) arg1 = self.process_variable(remaining_text, meta.line) # FH, 2022 this used to be str but convert_numerals in needed to accept non-latin numbers @@ -1936,11 +2590,11 @@ def assign(self, meta, args): def process_token_or_tree(self, argument): if type(argument) is Tree: - return f'{str(argument.children[0])}' + return f"{str(argument.children[0])}" if argument.isnumeric(): latin_numeral = int(argument) - return f'int({latin_numeral})' - return f'int({argument})' + return f"int({latin_numeral})" + return f"int({argument})" def process_calculation(self, args, operator): # arguments of a sum are either a token or a @@ -1949,19 +2603,19 @@ def process_calculation(self, args, operator): # for tokens we add int around them args = [self.process_token_or_tree(a) for a in args] - return Tree('sum', [f'{args[0]} {operator} {args[1]}']) + return Tree("sum", [f"{args[0]} {operator} {args[1]}"]) def addition(self, meta, args): - return self.process_calculation(args, '+') + return self.process_calculation(args, "+") def subtraction(self, meta, args): - return self.process_calculation(args, '-') + return self.process_calculation(args, "-") def multiplication(self, meta, args): - return self.process_calculation(args, '*') + return self.process_calculation(args, "*") def division(self, meta, args): - return self.process_calculation(args, '//') + return self.process_calculation(args, "//") def turn(self, meta, args): if len(args) == 0: @@ -1975,7 +2629,7 @@ def turn(self, meta, args): def forward(self, meta, args): if len(args) == 0: - return sleep_after('t.forward(50)', False) + return sleep_after("t.forward(50)", False) arg = args[0] if self.is_variable(arg): return self.make_forward(escape_var(arg)) @@ -1986,7 +2640,9 @@ def forward(self, meta, args): def sleep_after(commands, indent=True): lines = commands.split() - if lines[-1] == "time.sleep(0.1)": # we don't sleep double so skip if final line is a sleep already + if ( + lines[-1] == "time.sleep(0.1)" + ): # we don't sleep double so skip if final line is a sleep already return commands sleep_command = "time.sleep(0.1)" if indent is False else " time.sleep(0.1)" @@ -1998,7 +2654,7 @@ def sleep_after(commands, indent=True): @source_map_transformer(source_map) class ConvertToPython_7(ConvertToPython_6): def repeat(self, meta, args): - var_name = self.get_fresh_var('__i__') + var_name = self.get_fresh_var("__i__") times = self.process_variable(args[0], meta.line) command = args[1] # in level 7, repeats can only have 1 line as their arguments @@ -2013,7 +2669,6 @@ def repeat(self, meta, args): @hedy_transpiler(level=9) @source_map_transformer(source_map) class ConvertToPython_8_9(ConvertToPython_7): - def command(self, meta, args): return "".join(args) @@ -2021,7 +2676,7 @@ def repeat(self, meta, args): # todo fh, may 2022, could be merged with 7 if we make # indent a boolean parameter? - var_name = self.get_fresh_var('i') + var_name = self.get_fresh_var("i") times = self.process_variable(args[0], meta.line) all_lines = [ConvertToPython.indent(x) for x in args[1:]] @@ -2037,44 +2692,59 @@ def ifs(self, meta, args): def ifpressed(self, met, args): args = [a for a in args if a != ""] # filter out in|dedent tokens - all_lines = '\n'.join([x for x in args[1:]]) + all_lines = "\n".join([x for x in args[1:]]) all_lines = ConvertToPython.indent(all_lines) var_or_key = args[0] # if this is a variable, we assume it is a key (for now) if self.is_variable(var_or_key): - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.unicode == {args[0]}: {all_lines} - break""", button=False) + break""", + button=False, + ) elif len(var_or_key) == 1: # one character? also a key! - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.unicode == '{args[0]}': {all_lines} - break""", button=False) + break""", + button=False, + ) else: # otherwise we mean a button button_name = self.process_variable(args[0], met.line) - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.key == {button_name}: {all_lines} - break""", button=True) + break""", + button=True, + ) def ifpressed_else(self, met, args): args = [a for a in args if a != ""] # filter out in|dedent tokens - all_lines = '\n'.join([x for x in args[1:]]) + all_lines = "\n".join([x for x in args[1:]]) all_lines = ConvertToPython.indent(all_lines) - if (len(args[0]) > 1): + if len(args[0]) > 1: button_name = self.process_variable(args[0], met.line) - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.key == {button_name}: {all_lines} - break""", button=True) + break""", + button=True, + ) else: - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.unicode == '{args[0]}': {all_lines} - break""", button=False) + break""", + button=False, + ) def elses(self, meta, args): args = [a for a in args if a != ""] # filter out in|dedent tokens @@ -2086,9 +2756,7 @@ def ifpressed_elses(self, meta, args): args = [a for a in args if a != ""] # filter out in|dedent tokens args += [" break"] - all_lines = "\n".join( - [ConvertToPython.indent(x, 4) for x in args] - ) + all_lines = "\n".join([ConvertToPython.indent(x, 4) for x in args]) return all_lines @@ -2097,8 +2765,11 @@ def var_access(self, meta, args): return escape_var(args[0]) else: # this is list_access - return escape_var(args[0]) + "[" + str(escape_var(args[1])) + "]" if type(args[1] - ) is not Tree else "random.choice(" + str(escape_var(args[0])) + ")" + return ( + escape_var(args[0]) + "[" + str(escape_var(args[1])) + "]" + if type(args[1]) is not Tree + else "random.choice(" + str(escape_var(args[0])) + ")" + ) def var_access_print(self, meta, args): return self.var_access(meta, args) @@ -2127,7 +2798,7 @@ def for_loop(self, meta, args): iterator = escape_var(args[0]) body = "\n".join([ConvertToPython.indent(x) for x in args[3:]]) body = sleep_after(body) - stepvar_name = self.get_fresh_var('step') + stepvar_name = self.get_fresh_var("step") begin = self.process_token_or_tree(args[1]) end = self.process_token_or_tree(args[2]) return f"""{stepvar_name} = 1 if {begin} < {end} else -1 @@ -2141,11 +2812,14 @@ def for_loop(self, meta, args): class ConvertToPython_12(ConvertToPython_11): def define(self, meta, args): function_name = args[0] - args_str = ", ".join(str(x) for x in args[1].children) if isinstance( - args[1], Tree) and args[1].data == "arguments" else "" + args_str = ( + ", ".join(str(x) for x in args[1].children) + if isinstance(args[1], Tree) and args[1].data == "arguments" + else "" + ) lines = [] - for line in args[1 if args_str == "" else 2:]: + for line in args[1 if args_str == "" else 2 :]: lines.append(line) body = "\n".join(ConvertToPython.indent(x) for x in lines) @@ -2154,7 +2828,10 @@ def define(self, meta, args): def call(self, meta, args): args_str = "" if len(args) > 1: - args_str = ", ".join(str(x.children[0]) if isinstance(x, Tree) else str(x) for x in args[1].children) + args_str = ", ".join( + str(x.children[0]) if isinstance(x, Tree) else str(x) + for x in args[1].children + ) return f"{args[0]}({args_str})" def returns(self, meta, args): @@ -2167,19 +2844,19 @@ def number(self, meta, args): try: all_int = [str(int(x)) == x for x in args] if all(all_int): - return ''.join(args) + return "".join(args) else: # int succeeds but does not return the same? these are non-latin numbers # and need to be casted - return ''.join([str(int(x)) for x in args]) + return "".join([str(int(x)) for x in args]) except Exception: # if not? make into all floats numbers = [str(float(x)) for x in args] - return ''.join(numbers) + return "".join(numbers) def NEGATIVE_NUMBER(self, meta, args): numbers = [str(float(x)) for x in args] - return ''.join(numbers) + return "".join(numbers) def text_in_quotes(self, meta, args): # We need to re-add the quotes, so that the Python code becomes name = 'Jan' or "Jan's" @@ -2190,7 +2867,7 @@ def text_in_quotes(self, meta, args): def process_token_or_tree(self, argument): if isinstance(argument, Tree): - return f'{str(argument.children[0])}' + return f"{str(argument.children[0])}" else: return argument @@ -2210,7 +2887,8 @@ def ask(self, meta, args): argument_string = self.print_ask_args(meta, args[1:]) assign = f"{var} = input(f'''{argument_string}''')" - return textwrap.dedent(f"""\ + return textwrap.dedent( + f"""\ {assign} try: {var} = int({var}) @@ -2218,7 +2896,8 @@ def ask(self, meta, args): try: {var} = float({var}) except ValueError: - pass""") # no number? leave as string + pass""" + ) # no number? leave as string def assign_list(self, meta, args): parameter = args[0] @@ -2238,11 +2917,14 @@ def assign(self, meta, args): except exceptions.UndefinedVarException: # is the text a number? then no quotes are fine. if not, raise maar! - if not (ConvertToPython.is_int(right_hand_side) or ConvertToPython.is_float( - right_hand_side) or ConvertToPython.is_random(right_hand_side)): + if not ( + ConvertToPython.is_int(right_hand_side) + or ConvertToPython.is_float(right_hand_side) + or ConvertToPython.is_random(right_hand_side) + ): raise exceptions.UnquotedAssignTextException( - text=args[1], - line_number=meta.line) + text=args[1], line_number=meta.line + ) if isinstance(right_hand_side, Tree): exception = self.make_catch_exception([right_hand_side.children[0]]) @@ -2269,7 +2951,7 @@ def turn(self, meta, args): def forward(self, meta, args): if len(args) == 0: - return sleep_after('t.forward(50)', False) + return sleep_after("t.forward(50)", False) arg = args[0] if self.is_variable(arg): return self.make_forward(escape_var(arg)) @@ -2278,13 +2960,17 @@ def forward(self, meta, args): return self.make_forward(float(args[0])) def make_turn(self, parameter): - return self.make_turtle_command(parameter, Command.turn, 'right', False, 'float') + return self.make_turtle_command( + parameter, Command.turn, "right", False, "float" + ) def make_forward(self, parameter): - return self.make_turtle_command(parameter, Command.forward, 'forward', True, 'float') + return self.make_turtle_command( + parameter, Command.forward, "forward", True, "float" + ) def division(self, meta, args): - return self.process_calculation(args, '/') + return self.process_calculation(args, "/") @v_args(meta=True) @@ -2292,10 +2978,10 @@ def division(self, meta, args): @source_map_transformer(source_map) class ConvertToPython_13(ConvertToPython_12): def and_condition(self, meta, args): - return ' and '.join(args) + return " and ".join(args) def or_condition(self, meta, args): - return ' or '.join(args) + return " or ".join(args) @v_args(meta=True) @@ -2303,7 +2989,6 @@ def or_condition(self, meta, args): @source_map_transformer(source_map) class ConvertToPython_14(ConvertToPython_13): def process_comparison(self, meta, args, operator): - arg0 = self.process_variable_for_comparisons(args[0]) arg1 = self.process_variable_for_comparisons(args[1]) @@ -2353,20 +3038,29 @@ def ifpressed(self, meta, args): # for now we assume a var is a letter, we can check this lateron by searching for a ... = button if self.is_variable(var_or_button): - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.unicode == {args[0]}: {body} - break""", button=False) + break""", + button=False, + ) elif len(var_or_button) > 1: - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.key == {button_name}: {body} - break""", button=True) + break""", + button=True, + ) else: - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.unicode == '{args[0]}': {body} - break""", button=False) + break""", + button=False, + ) @v_args(meta=True) @@ -2379,16 +3073,20 @@ def assign_list(self, meta, args): return parameter + " = [" + ", ".join(values) + "]" def change_list_item(self, meta, args): - left_side = args[0] + '[' + args[1] + '-1]' + left_side = args[0] + "[" + args[1] + "-1]" right_side = args[2] - exception_text = gettext('catch_index_exception').replace('{list_name}', style_command(args[0])) - exception = textwrap.dedent(f"""\ + exception_text = gettext("catch_index_exception").replace( + "{list_name}", style_command(args[0]) + ) + exception = textwrap.dedent( + f"""\ try: {left_side} except IndexError: raise Exception('{exception_text}') - """) - return exception + left_side + ' = ' + right_side + """ + ) + return exception + left_side + " = " + right_side def ifs(self, meta, args): all_lines = [ConvertToPython.indent(x) for x in args[1:]] @@ -2408,26 +3106,38 @@ def elifs(self, meta, args): def ifpressed_elifs(self, meta, args): args = [a for a in args if a != ""] # filter out in|dedent tokens - all_lines = '\n'.join([x for x in args[1:]]) + all_lines = "\n".join([x for x in args[1:]]) all_lines = ConvertToPython.indent(all_lines) var_or_key = args[0] # if this is a variable, we assume it is a key (for now) if self.is_variable(var_or_key): - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.unicode == {args[0]}: {all_lines} - break""", button=False, add_command_prefix=False) + break""", + button=False, + add_command_prefix=False, + ) elif len(var_or_key) == 1: # one character? also a key! - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.unicode == '{args[0]}': {all_lines} - break""", button=False, add_command_prefix=False) + break""", + button=False, + add_command_prefix=False, + ) else: # otherwise we mean a button button_name = self.process_variable(args[0], meta.line) - return self.make_ifpressed_command(f"""\ + return self.make_ifpressed_command( + f"""\ if event.key == {button_name}: {all_lines} - break""", button=True, add_command_prefix=False) + break""", + button=True, + add_command_prefix=False, + ) @v_args(meta=True) @@ -2456,34 +3166,36 @@ def merge_grammars(grammar_text_1, grammar_text_2, level): # rules that are new in the second file are added (remaining_rules_grammar_2) merged_grammar = [] - rules_grammar_1 = grammar_text_1.split('\n') - remaining_rules_grammar_2 = grammar_text_2.split('\n') + rules_grammar_1 = grammar_text_1.split("\n") + remaining_rules_grammar_2 = grammar_text_2.split("\n") for line_1 in rules_grammar_1: - if line_1 == '' or line_1[0] == '/': # skip comments and empty lines: + if line_1 == "" or line_1[0] == "/": # skip comments and empty lines: continue - parts = line_1.split(':') + parts = line_1.split(":") # get part before are after : (this is a join because there can be : in the rule) - name_1, definition_1 = parts[0], ''.join(parts[1:]) + name_1, definition_1 = parts[0], "".join(parts[1:]) - rules_grammar_2 = grammar_text_2.split('\n') + rules_grammar_2 = grammar_text_2.split("\n") override_found = False for line_2 in rules_grammar_2: - if line_2 == '' or line_2[0] == '/': # skip comments and empty lines: + if line_2 == "" or line_2[0] == "/": # skip comments and empty lines: continue - needs_preprocessing = re.match(r'((\w|_)+)<((\w|_)+)>', line_2) + needs_preprocessing = re.match(r"((\w|_)+)<((\w|_)+)>", line_2) if needs_preprocessing: - name_2 = f'{needs_preprocessing.group(1)}' + name_2 = f"{needs_preprocessing.group(1)}" processor = needs_preprocessing.group(3) else: - parts = line_2.split(':') - name_2, definition_2 = parts[0], ''.join(parts[1]) # get part before are after : + parts = line_2.split(":") + name_2, definition_2 = parts[0], "".join( + parts[1] + ) # get part before are after : if name_1 == name_2: override_found = True if needs_preprocessing: definition_2 = PREPROCESS_RULES[processor](definition_1) - line_2_processed = f'{name_2}: {definition_2}' + line_2_processed = f"{name_2}: {definition_2}" else: line_2_processed = line_2 if definition_1.strip() == definition_2.strip(): @@ -2491,7 +3203,9 @@ def merge_grammars(grammar_text_1, grammar_text_2, level): warnings.warn(warn_message) # Used to compute the rules that use the merge operators in the grammar # namely +=, -= and > - new_rule = merge_rules_operator(definition_1, definition_2, name_1, line_2_processed) + new_rule = merge_rules_operator( + definition_1, definition_2, name_1, line_2_processed + ) # Already procesed so remove it remaining_rules_grammar_2.remove(line_2) break @@ -2503,32 +3217,38 @@ def merge_grammars(grammar_text_1, grammar_text_2, level): # all rules that were not overlapping are new in the grammar, add these too for rule in remaining_rules_grammar_2: - if not (rule == '' or rule[0] == '/'): + if not (rule == "" or rule[0] == "/"): merged_grammar.append(rule) merged_grammar = sorted(merged_grammar) - return '\n'.join(merged_grammar) + return "\n".join(merged_grammar) def merge_rules_operator(prev_definition, new_definition, name, complete_line): # Check if the rule is adding or substracting new rules - has_add_op = new_definition.startswith('+=') - has_sub_op = has_add_op and '-=' in new_definition - has_last_op = has_add_op and '>' in new_definition + has_add_op = new_definition.startswith("+=") + has_sub_op = has_add_op and "-=" in new_definition + has_last_op = has_add_op and ">" in new_definition if has_sub_op: # Get the rules we need to substract - part_list = new_definition.split('-=') - add_list, sub_list = (part_list[0], part_list[1]) if has_sub_op else (part_list[0], '') + part_list = new_definition.split("-=") + add_list, sub_list = ( + (part_list[0], part_list[1]) if has_sub_op else (part_list[0], "") + ) add_list = add_list[3:] # Get the rules that need to be last - sub_list = sub_list.split('>') - sub_list, last_list = (sub_list[0], sub_list[1]) if has_last_op else (sub_list[0], '') - sub_list = sub_list + '|' + last_list + sub_list = sub_list.split(">") + sub_list, last_list = ( + (sub_list[0], sub_list[1]) if has_last_op else (sub_list[0], "") + ) + sub_list = sub_list + "|" + last_list result_cmd_list = get_remaining_rules(prev_definition, sub_list) elif has_add_op: # Get the rules that need to be last - part_list = new_definition.split('>') - add_list, sub_list = (part_list[0], part_list[1]) if has_last_op else (part_list[0], '') + part_list = new_definition.split(">") + add_list, sub_list = ( + (part_list[0], part_list[1]) if has_last_op else (part_list[0], "") + ) add_list = add_list[3:] last_list = sub_list result_cmd_list = get_remaining_rules(prev_definition, sub_list) @@ -2545,10 +3265,10 @@ def merge_rules_operator(prev_definition, new_definition, name, complete_line): def get_remaining_rules(orig_def, sub_def): - orig_cmd_list = [command.strip() for command in orig_def.split('|')] - unwanted_cmd_list = [command.strip() for command in sub_def.split('|')] + orig_cmd_list = [command.strip() for command in orig_def.split("|")] + unwanted_cmd_list = [command.strip() for command in sub_def.split("|")] result_cmd_list = [cmd for cmd in orig_cmd_list if cmd not in unwanted_cmd_list] - result_cmd_list = ' | '.join(result_cmd_list) # turn the result list into a string + result_cmd_list = " | ".join(result_cmd_list) # turn the result list into a string return result_cmd_list @@ -2567,79 +3287,108 @@ def create_grammar(level, lang="en"): if source_map.skip_faulty: # Make sure to change the meaning of error_invalid # this way more text will be 'catched' - error_invalid_rules = re.findall(r'^error_invalid.-100:.*?\n', result, re.MULTILINE) + error_invalid_rules = re.findall( + r"^error_invalid.-100:.*?\n", result, re.MULTILINE + ) if len(error_invalid_rules) > 0: error_invalid_rule = error_invalid_rules[0] - error_invalid_rule_changed = 'error_invalid.-100: textwithoutspaces _SPACE* text?\n' + error_invalid_rule_changed = ( + "error_invalid.-100: textwithoutspaces _SPACE* text?\n" + ) result = result.replace(error_invalid_rule, error_invalid_rule_changed) # from level 12: # Make sure that all keywords in the language are added to the rules: # textwithspaces & textwithoutspaces, so that these do not fall into the error_invalid rule if level > 12: - textwithspaces_rules = re.findall(r'^textwithspaces:.*?\n', result, re.MULTILINE) + textwithspaces_rules = re.findall( + r"^textwithspaces:.*?\n", result, re.MULTILINE + ) if len(textwithspaces_rules) > 0: textwithspaces_rule = textwithspaces_rules[0] - textwithspaces_rule_changed = r'textwithspaces: /(?:[^#\n،,,、 ]| (?!SKIP1))+/ -> text' + '\n' - result = result.replace(textwithspaces_rule, textwithspaces_rule_changed) + textwithspaces_rule_changed = ( + r"textwithspaces: /(?:[^#\n،,,、 ]| (?!SKIP1))+/ -> text" + "\n" + ) + result = result.replace( + textwithspaces_rule, textwithspaces_rule_changed + ) - textwithoutspaces_rules = re.findall(r'^textwithoutspaces:.*?\n', result, re.MULTILINE) + textwithoutspaces_rules = re.findall( + r"^textwithoutspaces:.*?\n", result, re.MULTILINE + ) if len(textwithoutspaces_rules) > 0: textwithoutspaces_rule = textwithoutspaces_rules[0] textwithoutspaces_rule_changed = ( - r'textwithoutspaces: /(?:[^#\n،,,、 *+\-\/eiіиలేไamfnsbअ否אو]|SKIP2)+/ -> text' + '\n' + r"textwithoutspaces: /(?:[^#\n،,,、 *+\-\/eiіиలేไamfnsbअ否אو]|SKIP2)+/ -> text" + + "\n" + ) + result = result.replace( + textwithoutspaces_rule, textwithoutspaces_rule_changed ) - result = result.replace(textwithoutspaces_rule, textwithoutspaces_rule_changed) non_allowed_words = re.findall(r'".*?"', keywords) non_allowed_words = list(set(non_allowed_words)) - non_allowed_words = [x.replace('"', '') for x in non_allowed_words] - non_allowed_words_with_space = '|'.join(non_allowed_words) - result = result.replace('SKIP1', non_allowed_words_with_space) + non_allowed_words = [x.replace('"', "") for x in non_allowed_words] + non_allowed_words_with_space = "|".join(non_allowed_words) + result = result.replace("SKIP1", non_allowed_words_with_space) letters_done = [] - string_words = '' + string_words = "" for word in non_allowed_words: # go through all words and add them in groups by their first letter first_letter = word[0] if first_letter not in letters_done: - string_words += f'|{first_letter}(?!{word[1:]})' + string_words += f"|{first_letter}(?!{word[1:]})" letters_done.append(first_letter) else: - string_words = string_words.replace(f'|{word[0]}(?!', f'|{word[0]}(?!{word[1:]}|') + string_words = string_words.replace( + f"|{word[0]}(?!", f"|{word[0]}(?!{word[1:]}|" + ) - string_words = string_words.replace('|)', ')') # remove empty regex expressions + string_words = string_words.replace( + "|)", ")" + ) # remove empty regex expressions string_words = string_words[1:] # remove first | - result = result.replace('SKIP2', string_words) + result = result.replace("SKIP2", string_words) # Make sure that the error_invalid is added to the command rule # to function as a 'bucket' for faulty text - command_rules = re.findall(r'^command:.*?\n', result, re.MULTILINE) + command_rules = re.findall(r"^command:.*?\n", result, re.MULTILINE) if len(command_rules) > 0: command_rule = command_rules[0] - command_rule_with_error_invalid = command_rule.replace('\n', '') + " | error_invalid\n" + command_rule_with_error_invalid = ( + command_rule.replace("\n", "") + " | error_invalid\n" + ) result = result.replace(command_rule, command_rule_with_error_invalid) # Make sure that the error_invalid is added to the if_less_command rule # to function as a 'bucket' for faulty if body commands - if_less_command_rules = re.findall(r'^_if_less_command:.*?\n', result, re.MULTILINE) + if_less_command_rules = re.findall( + r"^_if_less_command:.*?\n", result, re.MULTILINE + ) if len(if_less_command_rules) > 0: if_less_command_rule = if_less_command_rules[0] - if_less_command_rule_with_error_invalid = if_less_command_rule.replace('\n', '') + " | error_invalid\n" - result = result.replace(if_less_command_rule, if_less_command_rule_with_error_invalid) + if_less_command_rule_with_error_invalid = ( + if_less_command_rule.replace("\n", "") + " | error_invalid\n" + ) + result = result.replace( + if_less_command_rule, if_less_command_rule_with_error_invalid + ) # Make sure that the _non_empty_program rule does not contain error_invalid rules # so that all errors will be catches by error_invalid instead of _non_empty_program_skipping - non_empty_program_rules = re.findall(r'^_non_empty_program:.*?\n', result, re.MULTILINE) + non_empty_program_rules = re.findall( + r"^_non_empty_program:.*?\n", result, re.MULTILINE + ) if len(non_empty_program_rules) > 0: non_empty_program_rule = non_empty_program_rules[0] - non_empty_program_rule_changed = ( - '_non_empty_program: _EOL* (command) _SPACE* (_EOL+ command _SPACE*)* _EOL*\n' + non_empty_program_rule_changed = "_non_empty_program: _EOL* (command) _SPACE* (_EOL+ command _SPACE*)* _EOL*\n" + result = result.replace( + non_empty_program_rule, non_empty_program_rule_changed ) - result = result.replace(non_empty_program_rule, non_empty_program_rule_changed) # ready? Save to file to ease debugging # this could also be done on each merge for performance reasons @@ -2663,7 +3412,9 @@ def get_additional_rules_for_level(level, sub=0): filename = "level" + str(level) + "-" + str(sub) + "-Additions.lark" else: filename = "level" + str(level) + "-Additions.lark" - with open(path.join(script_dir, "grammars", filename), "r", encoding="utf-8") as file: + with open( + path.join(script_dir, "grammars", filename), "r", encoding="utf-8" + ) as file: grammar_text = file.read() return grammar_text @@ -2671,10 +3422,13 @@ def get_additional_rules_for_level(level, sub=0): def get_full_grammar_for_level(level): script_dir = path.abspath(path.dirname(__file__)) filename = "level" + str(level) + ".lark" - with open(path.join(script_dir, "grammars", filename), "r", encoding="utf-8") as file: + with open( + path.join(script_dir, "grammars", filename), "r", encoding="utf-8" + ) as file: grammar_text = file.read() return grammar_text + # TODO FH, May 2022. I feel there are other places in the code where we also do this # opportunity to combine? @@ -2685,11 +3439,15 @@ def get_keywords_for_language(language): if not local_keywords_enabled: raise FileNotFoundError("Local keywords are not enabled") filename = "keywords-" + str(language) + ".lark" - with open(path.join(script_dir, "grammars", filename), "r", encoding="utf-8") as file: + with open( + path.join(script_dir, "grammars", filename), "r", encoding="utf-8" + ) as file: keywords = file.read() except FileNotFoundError: filename = "keywords-en.lark" - with open(path.join(script_dir, "grammars", filename), "r", encoding="utf-8") as file: + with open( + path.join(script_dir, "grammars", filename), "r", encoding="utf-8" + ) as file: keywords = file.read() return keywords @@ -2698,25 +3456,37 @@ def get_keywords_for_language(language): def get_parser(level, lang="en", keep_all_tokens=False): - """Return the Lark parser for a given level. - """ - key = str(level) + "." + lang + '.' + str(keep_all_tokens) + '.' + str(source_map.skip_faulty) + """Return the Lark parser for a given level.""" + key = ( + str(level) + + "." + + lang + + "." + + str(keep_all_tokens) + + "." + + str(source_map.skip_faulty) + ) existing = PARSER_CACHE.get(key) if existing and not utils.is_debug_mode(): return existing grammar = create_grammar(level, lang) - ret = Lark(grammar, regex=True, propagate_positions=True, keep_all_tokens=keep_all_tokens) # ambiguity='explicit' + ret = Lark( + grammar, regex=True, propagate_positions=True, keep_all_tokens=keep_all_tokens + ) # ambiguity='explicit' PARSER_CACHE[key] = ret return ret -ParseResult = namedtuple('ParseResult', ['code', 'source_map', 'has_turtle', 'has_pygame']) +ParseResult = namedtuple( + "ParseResult", ["code", "source_map", "has_turtle", "has_pygame"] +) def transpile_inner_with_skipping_faulty(input_string, level, lang="en"): - def skipping_faulty(meta, args): return [True] + def skipping_faulty(meta, args): + return [True] - defined_errors = [method for method in dir(IsValid) if method.startswith('error')] + defined_errors = [method for method in dir(IsValid) if method.startswith("error")] defined_errors_original = dict() def set_error_to_allowed(): @@ -2732,7 +3502,9 @@ def set_errors_to_original(): try: set_error_to_allowed() - transpile_result = transpile_inner(input_string, level, lang, populate_source_map=True) + transpile_result = transpile_inner( + input_string, level, lang, populate_source_map=True + ) finally: # make sure to always revert IsValid methods to original set_errors_to_original() @@ -2742,9 +3514,11 @@ def set_errors_to_original(): at_least_one_error_found = False for hedy_source_code, python_source_code in source_map.map.copy().items(): - if hedy_source_code.error is not None or python_source_code.code == 'pass': + if hedy_source_code.error is not None or python_source_code.code == "pass": try: - transpile_inner(hedy_source_code.code, source_map.level, source_map.language) + transpile_inner( + hedy_source_code.code, source_map.level, source_map.language + ) except Exception as e: hedy_source_code.error = e @@ -2752,7 +3526,7 @@ def set_errors_to_original(): at_least_one_error_found = True if not at_least_one_error_found: - raise Exception('Could not find original error for skipped code') + raise Exception("Could not find original error for skipped code") return transpile_result @@ -2771,9 +3545,13 @@ def transpile(input_string, level, lang="en", skip_faulty=False): if not skip_faulty: try: source_map.set_skip_faulty(False) - transpile_result = transpile_inner(input_string, level, lang, populate_source_map=True) + transpile_result = transpile_inner( + input_string, level, lang, populate_source_map=True + ) except Exception as original_error: - source_map.exception_found_during_parsing = original_error # store original exception + source_map.exception_found_during_parsing = ( + original_error # store original exception + ) raise original_error else: original_error = source_map.exception_found_during_parsing @@ -2784,7 +3562,9 @@ def transpile(input_string, level, lang="en", skip_faulty=False): try: source_map.set_skip_faulty(True) - transpile_result = transpile_inner_with_skipping_faulty(input_string, level, lang) + transpile_result = transpile_inner_with_skipping_faulty( + input_string, level, lang + ) except Exception: raise original_error # we could not skip faulty code, raise original exception @@ -2794,30 +3574,30 @@ def transpile(input_string, level, lang="en", skip_faulty=False): def translate_characters(s): # this method is used to make it more clear to kids what is meant in error messages # for example ' ' is hard to read, space is easier - commas = [',', "،", ",", "、"] - if s == ' ': - return 'space' + commas = [",", "،", ",", "、"] + if s == " ": + return "space" elif s in commas: - return 'comma' - elif s == '?': - return 'question mark' - elif s == '\n': - return 'newline' - elif s == '.': - return 'period' - elif s == '!': - return 'exclamation mark' - elif s == '*': - return 'star' + return "comma" + elif s == "?": + return "question mark" + elif s == "\n": + return "newline" + elif s == ".": + return "period" + elif s == "!": + return "exclamation mark" + elif s == "*": + return "star" elif s == "'": - return 'single quotes' + return "single quotes" elif s == '"': - return 'double quotes' - elif s == '/': - return 'slash' - elif s == '-': - return 'dash' - elif s >= 'a' and s <= 'z' or s >= 'A' and s <= 'Z': + return "double quotes" + elif s == "/": + return "slash" + elif s == "-": + return "dash" + elif s >= "a" and s <= "z" or s >= "A" and s <= "Z": return s else: return s @@ -2831,7 +3611,7 @@ def beautify_parse_error(character_found): def find_indent_length(line): number_of_spaces = 0 for x in line: - if x == ' ': + if x == " ": number_of_spaces += 1 else: break @@ -2843,8 +3623,10 @@ def line_requires_indentation(line, lang): # because now a line like `repeat is 5` would also require indentation! line = line.lstrip() # remove spaces since also ` for ` requires indentation - if lang not in indent_keywords.keys(): # some language like Greek or Czech do not have local keywords - lang = 'en' + if ( + lang not in indent_keywords.keys() + ): # some language like Greek or Czech do not have local keywords + lang = "en" local_indent_keywords = indent_keywords[lang] @@ -2852,9 +3634,10 @@ def line_requires_indentation(line, lang): # does the line start with this keyword? # We can't just split since some langs like French have keywords containing a space # We also have to check space/lineending/: after or forward 100 wil also require indentation - end_of_line_or_word = (len(line) > len(k) and ( - line[len(k)] == " " or line[len(k)] == ":")) or len(line) == len(k) - if end_of_line_or_word and line[:len(k)] == k: + end_of_line_or_word = ( + len(line) > len(k) and (line[len(k)] == " " or line[len(k)] == ":") + ) or len(line) == len(k) + if end_of_line_or_word and line[: len(k)] == k: return True return False @@ -2869,8 +3652,10 @@ def preprocess_blocks(code, level, lang): line_number = 0 next_line_needs_indentation = False for line in lines: - if ' _ ' in line or line == '_': - raise hedy.exceptions.CodePlaceholdersPresentException(line_number=line_number+1) + if " _ " in line or line == "_": + raise hedy.exceptions.CodePlaceholdersPresentException( + line_number=line_number + 1 + ) leading_spaces = find_indent_length(line) @@ -2878,7 +3663,7 @@ def preprocess_blocks(code, level, lang): # ignore whitespace-only lines if leading_spaces == len(line): - processed_code.append('') + processed_code.append("") continue # first encounter sets indent size for this program @@ -2890,43 +3675,83 @@ def preprocess_blocks(code, level, lang): if (leading_spaces % indent_size) != 0: # there is inconsistent indentation, not sure if that is too much or too little! if leading_spaces < current_number_of_indents * indent_size: - fixed_code = program_repair.fix_indent(code, line_number, leading_spaces, indent_size) - raise hedy.exceptions.NoIndentationException(line_number=line_number, leading_spaces=leading_spaces, - indent_size=indent_size, fixed_code=fixed_code) + fixed_code = program_repair.fix_indent( + code, line_number, leading_spaces, indent_size + ) + raise hedy.exceptions.NoIndentationException( + line_number=line_number, + leading_spaces=leading_spaces, + indent_size=indent_size, + fixed_code=fixed_code, + ) else: - fixed_code = program_repair.fix_indent(code, line_number, leading_spaces, indent_size) - raise hedy.exceptions.IndentationException(line_number=line_number, leading_spaces=leading_spaces, - indent_size=indent_size, fixed_code=fixed_code) + fixed_code = program_repair.fix_indent( + code, line_number, leading_spaces, indent_size + ) + raise hedy.exceptions.IndentationException( + line_number=line_number, + leading_spaces=leading_spaces, + indent_size=indent_size, + fixed_code=fixed_code, + ) # happy path, multiple of 4 spaces: current_number_of_indents = leading_spaces // indent_size if current_number_of_indents > 1 and level == hedy.LEVEL_STARTING_INDENTATION: - raise hedy.exceptions.LockedLanguageFeatureException(concept="nested blocks") + raise hedy.exceptions.LockedLanguageFeatureException( + concept="nested blocks" + ) - if current_number_of_indents > previous_number_of_indents and not next_line_needs_indentation: + if ( + current_number_of_indents > previous_number_of_indents + and not next_line_needs_indentation + ): # we are indenting, but this line is not following* one that even needs indenting, raise # * note that we have not yet updated the value of 'next line needs indenting' so if refers to this line! - fixed_code = program_repair.fix_indent(code, line_number, leading_spaces, indent_size) - raise hedy.exceptions.IndentationException(line_number=line_number, leading_spaces=leading_spaces, - indent_size=indent_size, fixed_code=fixed_code) + fixed_code = program_repair.fix_indent( + code, line_number, leading_spaces, indent_size + ) + raise hedy.exceptions.IndentationException( + line_number=line_number, + leading_spaces=leading_spaces, + indent_size=indent_size, + fixed_code=fixed_code, + ) - if next_line_needs_indentation and current_number_of_indents <= previous_number_of_indents: - fixed_code = program_repair.fix_indent(code, line_number, leading_spaces, indent_size) - raise hedy.exceptions.NoIndentationException(line_number=line_number, leading_spaces=leading_spaces, - indent_size=indent_size, fixed_code=fixed_code) + if ( + next_line_needs_indentation + and current_number_of_indents <= previous_number_of_indents + ): + fixed_code = program_repair.fix_indent( + code, line_number, leading_spaces, indent_size + ) + raise hedy.exceptions.NoIndentationException( + line_number=line_number, + leading_spaces=leading_spaces, + indent_size=indent_size, + fixed_code=fixed_code, + ) if current_number_of_indents - previous_number_of_indents > 1: - fixed_code = program_repair.fix_indent(code, line_number, leading_spaces, indent_size) - raise hedy.exceptions.IndentationException(line_number=line_number, leading_spaces=leading_spaces, - indent_size=indent_size, fixed_code=fixed_code) + fixed_code = program_repair.fix_indent( + code, line_number, leading_spaces, indent_size + ) + raise hedy.exceptions.IndentationException( + line_number=line_number, + leading_spaces=leading_spaces, + indent_size=indent_size, + fixed_code=fixed_code, + ) if current_number_of_indents < previous_number_of_indents: # we are dedenting ('jumping back) so we need to and an end-block # (multiple if multiple dedents are happening) - difference_in_indents = (previous_number_of_indents - current_number_of_indents) + difference_in_indents = ( + previous_number_of_indents - current_number_of_indents + ) for i in range(difference_in_indents): - processed_code[-1] += '#ENDBLOCK' + processed_code[-1] += "#ENDBLOCK" if line_requires_indentation(line, lang): next_line_needs_indentation = True @@ -2942,11 +3767,11 @@ def preprocess_blocks(code, level, lang): # if the last line is indented, the end of the program is also the end of all indents # so close all blocks for i in range(current_number_of_indents): - processed_code[-1] += '#ENDBLOCK' + processed_code[-1] += "#ENDBLOCK" return "\n".join(processed_code) -def preprocess_ifs(code, lang='en'): +def preprocess_ifs(code, lang="en"): processed_code = [] lines = code.split("\n") @@ -2955,30 +3780,38 @@ def starts_with(command, line): command_plus_translated_command = [command, KEYWORDS[lang].get(command)] for c in command_plus_translated_command: # starts with the keyword and next character is a space - if line[0:len(c)] == c and (len(c) == len(line) or line[len(c)] == ' '): + if line[0 : len(c)] == c and ( + len(c) == len(line) or line[len(c)] == " " + ): return True return False else: - return line[0:len(command)] == command + return line[0 : len(command)] == command def starts_with_after_repeat(command, line): elements_in_line = line.split() - keywords_in_lang = KEYWORDS.get(lang, KEYWORDS['en']) - repeat_plus_translated = ['repeat', keywords_in_lang.get('repeat')] - times_plus_translated = ['times', keywords_in_lang.get('times')] + keywords_in_lang = KEYWORDS.get(lang, KEYWORDS["en"]) + repeat_plus_translated = ["repeat", keywords_in_lang.get("repeat")] + times_plus_translated = ["times", keywords_in_lang.get("times")] - if len(elements_in_line) > 2 and elements_in_line[0] in repeat_plus_translated and elements_in_line[2] in times_plus_translated: - line = ' '.join(elements_in_line[3:]) + if ( + len(elements_in_line) > 2 + and elements_in_line[0] in repeat_plus_translated + and elements_in_line[2] in times_plus_translated + ): + line = " ".join(elements_in_line[3:]) if lang in ALL_KEYWORD_LANGUAGES: command_plus_translated_command = [command, KEYWORDS[lang].get(command)] for c in command_plus_translated_command: # starts with the keyword and next character is a space - if line[0:len(c)] == c and (len(c) == len(line) or line[len(c)] == ' '): + if line[0 : len(c)] == c and ( + len(c) == len(line) or line[len(c)] == " " + ): return True return False else: - return line[0:len(command)] == command + return line[0 : len(command)] == command def contains(command, line): if lang in ALL_KEYWORD_LANGUAGES: @@ -2994,7 +3827,9 @@ def contains_two(command, line): if lang in ALL_KEYWORD_LANGUAGES: command_plus_translated_command = [command, KEYWORDS[lang].get(command)] for c in command_plus_translated_command: - if line.count(' ' + c + ' ') >= 2: # surround in spaces since we dont want to mathc something like 'dishwasher is sophie' + if ( + line.count(" " + c + " ") >= 2 + ): # surround in spaces since we dont want to mathc something like 'dishwasher is sophie' return True return False @@ -3016,15 +3851,18 @@ def contains_any_of(commands, line): next_line = lines[i + 1] # if this line starts with if but does not contain an else, and the next line too is not an else. - if (starts_with('if', line) or starts_with_after_repeat('if', line)) and (not starts_with('else', next_line)) and (not contains('else', line)): + if ( + (starts_with("if", line) or starts_with_after_repeat("if", line)) + and (not starts_with("else", next_line)) + and (not contains("else", line)) + ): # is this line just a condition and no other keyword (because that is no problem) commands = ["print", "ask", "forward", "turn"] excluded_commands = ["pressed"] if ( - (contains_any_of(commands, line) or contains_two('is', line)) - and not contains_any_of(excluded_commands, line) - ): + contains_any_of(commands, line) or contains_two("is", line) + ) and not contains_any_of(excluded_commands, line): # a second command, but also no else in this line -> check next line! # no else in next line? @@ -3032,31 +3870,37 @@ def contains_any_of(commands, line): line = line + " else x__x__x__x is 5" processed_code.append(line) - processed_code.append(lines[-1]) # always add the last line (if it has if and no else that is no problem) + processed_code.append( + lines[-1] + ) # always add the last line (if it has if and no else that is no problem) return "\n".join(processed_code) def location_of_first_blank(code_snippet): # returns 0 if the code does not contain _ # otherwise returns the first location (line) of the blank - lines = code_snippet.split('\n') + lines = code_snippet.split("\n") for i in range(len(lines)): code = lines[i] if len(code) > 0: if (" _" in code) or ("_ " in code) or (code[-1] == "_"): - return i+1 + return i + 1 return 0 def check_program_size_is_valid(input_string): - number_of_lines = input_string.count('\n') + number_of_lines = input_string.count("\n") # parser is not made for huge programs! if number_of_lines > MAX_LINES: - raise exceptions.InputTooBigException(lines_of_code=number_of_lines, max_lines=MAX_LINES) + raise exceptions.InputTooBigException( + lines_of_code=number_of_lines, max_lines=MAX_LINES + ) -def process_input_string(input_string, level, lang, escape_backslashes=True, preprocess_ifs_enabled=True): - result = input_string.replace('\r\n', '\n') +def process_input_string( + input_string, level, lang, escape_backslashes=True, preprocess_ifs_enabled=True +): + result = input_string.replace("\r\n", "\n") location = location_of_first_blank(result) if location > 0: @@ -3079,10 +3923,12 @@ def process_input_string(input_string, level, lang, escape_backslashes=True, pre def parse_input(input_string, level, lang): parser = get_parser(level, lang) try: - parse_result = parser.parse(input_string + '\n') - return parse_result.children[0] # getting rid of the root could also be done in the transformer would be nicer + parse_result = parser.parse(input_string + "\n") + return parse_result.children[ + 0 + ] # getting rid of the root could also be done in the transformer would be nicer except lark.UnexpectedEOF: - lines = input_string.split('\n') + lines = input_string.split("\n") last_line = len(lines) raise exceptions.UnquotedEqualityCheck(line_number=last_line) except UnexpectedCharacters as e: @@ -3094,12 +3940,15 @@ def parse_input(input_string, level, lang): character_found = beautify_parse_error(e.char) # print(e.args[0]) # print(location, character_found, characters_expected) - fixed_code = program_repair.remove_unexpected_char(input_string, location[0], location[1]) + fixed_code = program_repair.remove_unexpected_char( + input_string, location[0], location[1] + ) raise exceptions.ParseException( level=level, location=location, found=character_found, - fixed_code=fixed_code) from e + fixed_code=fixed_code, + ) from e except UnexpectedEOF: # this one can't be beautified (for now), so give up :) raise e @@ -3122,8 +3971,7 @@ def is_program_valid(program_root, input_string, level, lang): line = invalid_info.line column = invalid_info.column - if invalid_info.error_type == ' ': - + if invalid_info.error_type == " ": # the error here is a space at the beginning of a line, we can fix that! fixed_code = program_repair.remove_leading_spaces(input_string) if fixed_code != input_string: # only if we have made a successful fix @@ -3131,62 +3979,90 @@ def is_program_valid(program_root, input_string, level, lang): fixed_result = transpile_inner(fixed_code, level, lang) result = fixed_result raise exceptions.InvalidSpaceException( - level=level, line_number=line, fixed_code=fixed_code, fixed_result=result) + level=level, + line_number=line, + fixed_code=fixed_code, + fixed_result=result, + ) except exceptions.HedyException: invalid_info.error_type = None transpile_inner(fixed_code, level) # The fixed code contains another error. Only report the original error for now. pass raise exceptions.InvalidSpaceException( - level=level, line_number=line, fixed_code=fixed_code, fixed_result=result) - elif invalid_info.error_type == 'invalid condition': + level=level, + line_number=line, + fixed_code=fixed_code, + fixed_result=result, + ) + elif invalid_info.error_type == "invalid condition": raise exceptions.UnquotedEqualityCheck(line_number=line) - elif invalid_info.error_type == 'invalid repeat': - raise exceptions.MissingInnerCommandException(command='repeat', level=level, line_number=line) - elif invalid_info.error_type == 'repeat missing print': - raise exceptions.IncompleteRepeatException(command='print', level=level, line_number=line) - elif invalid_info.error_type == 'repeat missing times': - raise exceptions.IncompleteRepeatException(command='times', level=level, line_number=line) - elif invalid_info.error_type == 'print without quotes': + elif invalid_info.error_type == "invalid repeat": + raise exceptions.MissingInnerCommandException( + command="repeat", level=level, line_number=line + ) + elif invalid_info.error_type == "repeat missing print": + raise exceptions.IncompleteRepeatException( + command="print", level=level, line_number=line + ) + elif invalid_info.error_type == "repeat missing times": + raise exceptions.IncompleteRepeatException( + command="times", level=level, line_number=line + ) + elif invalid_info.error_type == "print without quotes": unquotedtext = invalid_info.arguments[0] raise exceptions.UnquotedTextException( - level=level, unquotedtext=unquotedtext, line_number=invalid_info.line) - elif invalid_info.error_type == 'unsupported number': - raise exceptions.UnsupportedFloatException(value=''.join(invalid_info.arguments)) - elif invalid_info.error_type == 'lonely text': + level=level, unquotedtext=unquotedtext, line_number=invalid_info.line + ) + elif invalid_info.error_type == "unsupported number": + raise exceptions.UnsupportedFloatException( + value="".join(invalid_info.arguments) + ) + elif invalid_info.error_type == "lonely text": raise exceptions.LonelyTextException(level=level, line_number=line) - elif invalid_info.error_type == 'flat if': + elif invalid_info.error_type == "flat if": raise exceptions.WrongLevelException( - offending_keyword='if', + offending_keyword="if", working_level=7, - tip='no_more_flat_if', - line_number=invalid_info.line) - elif invalid_info.error_type == 'invalid at keyword': - raise exceptions.InvalidAtCommandException(command='at', level=level, line_number=invalid_info.line) - elif invalid_info.error_type == 'ifpressed missing else': + tip="no_more_flat_if", + line_number=invalid_info.line, + ) + elif invalid_info.error_type == "invalid at keyword": + raise exceptions.InvalidAtCommandException( + command="at", level=level, line_number=invalid_info.line + ) + elif invalid_info.error_type == "ifpressed missing else": raise exceptions.MissingElseForPressitException( - command='ifpressed_else', level=level, line_number=invalid_info.line) - elif invalid_info.error_type == 'nested function': + command="ifpressed_else", level=level, line_number=invalid_info.line + ) + elif invalid_info.error_type == "nested function": raise exceptions.NestedFunctionException() else: invalid_command = invalid_info.command - closest = closest_command(invalid_command, get_suggestions_for_language(lang, level)) + closest = closest_command( + invalid_command, get_suggestions_for_language(lang, level) + ) - if closest == 'keyword': # we couldn't find a suggestion - invalid_command_en = hedy_translation.translate_keyword_to_en(invalid_command, lang) + if closest == "keyword": # we couldn't find a suggestion + invalid_command_en = hedy_translation.translate_keyword_to_en( + invalid_command, lang + ) if invalid_command_en == Command.turn: arg = invalid_info.arguments[0][0] - raise hedy.exceptions.InvalidArgumentException(command=invalid_info.command, - allowed_types=get_allowed_types(Command.turn, level), - invalid_argument=arg, - line_number=invalid_info.line) + raise hedy.exceptions.InvalidArgumentException( + command=invalid_info.command, + allowed_types=get_allowed_types(Command.turn, level), + invalid_argument=arg, + line_number=invalid_info.line, + ) # clearly the error message here should be better or it should be a different one! - raise exceptions.ParseException(level=level, location=[line, column], found=invalid_command) + raise exceptions.ParseException( + level=level, location=[line, column], found=invalid_command + ) elif closest is None: raise exceptions.MissingCommandException(level=level, line_number=line) else: - fixed_code = None result = None fixed_code = input_string.replace(invalid_command, closest) @@ -3198,9 +4074,14 @@ def is_program_valid(program_root, input_string, level, lang): # The fixed code contains another error. Only report the original error for now. pass - raise exceptions.InvalidCommandException(invalid_command=invalid_command, level=level, - guessed_command=closest, line_number=line, - fixed_code=fixed_code, fixed_result=result) + raise exceptions.InvalidCommandException( + invalid_command=invalid_command, + level=level, + guessed_command=closest, + line_number=line, + fixed_code=fixed_code, + fixed_result=result, + ) def is_program_complete(abstract_syntax_tree, level): @@ -3209,8 +4090,9 @@ def is_program_complete(abstract_syntax_tree, level): incomplete_command_and_line = is_complete[1][0] incomplete_command = incomplete_command_and_line[0] line = incomplete_command_and_line[1] - raise exceptions.IncompleteCommandException(incomplete_command=incomplete_command, level=level, - line_number=line) + raise exceptions.IncompleteCommandException( + incomplete_command=incomplete_command, level=level, line_number=line + ) def create_lookup_table(abstract_syntax_tree, level, lang, input_string): @@ -3228,7 +4110,7 @@ def transpile_inner(input_string, level, lang="en", populate_source_map=False): level = int(level) if level > HEDY_MAX_LEVEL: - raise Exception(f'Levels over {HEDY_MAX_LEVEL} not implemented yet') + raise Exception(f"Levels over {HEDY_MAX_LEVEL} not implemented yet") input_string = process_input_string(input_string, level, lang) program_root = parse_input(input_string, level, lang) @@ -3250,7 +4132,9 @@ def transpile_inner(input_string, level, lang="en", populate_source_map=False): if not valid_echo(abstract_syntax_tree): raise exceptions.LonelyEchoException() - lookup_table = create_lookup_table(abstract_syntax_tree, level, lang, input_string) + lookup_table = create_lookup_table( + abstract_syntax_tree, level, lang, input_string + ) # FH, may 2022. for now, we just out arabic numerals when the language is ar # this can be changed into a profile setting or could be detected @@ -3262,7 +4146,9 @@ def transpile_inner(input_string, level, lang="en", populate_source_map=False): # grab the right transpiler from the lookup convertToPython = TRANSPILER_LOOKUP[level] - python = convertToPython(lookup_table, numerals_language).transform(abstract_syntax_tree) + python = convertToPython(lookup_table, numerals_language).transform( + abstract_syntax_tree + ) uses_turtle = UsesTurtle() has_turtle = uses_turtle.transform(abstract_syntax_tree) diff --git a/hedy_content.py b/hedy_content.py index 0f317740b6a..363356c8f29 100644 --- a/hedy_content.py +++ b/hedy_content.py @@ -17,239 +17,241 @@ # Todo TB -> We create this list manually, but it would be nice if we find # a way to automate this as well -NON_LATIN_LANGUAGES = ['ar', 'bg', 'bn', 'el', 'fa', 'hi', 'he', 'pa_PK', 'ru', 'zh_Hans'] +NON_LATIN_LANGUAGES = [ + "ar", + "bg", + "bn", + "el", + "fa", + "hi", + "he", + "pa_PK", + "ru", + "zh_Hans", +] # Babel has a different naming convention than Weblate and doesn't support some languages -> fix this manually -CUSTOM_BABEL_LANGUAGES = {'pa_PK': 'pa_Arab_PK', 'kmr': 'ku_TR', 'tn': 'en', 'tl': 'en'} +CUSTOM_BABEL_LANGUAGES = {"pa_PK": "pa_Arab_PK", "kmr": "ku_TR", "tn": "en", "tl": "en"} # For the non-existing language manually overwrite the display language to make sure it is displayed correctly -CUSTOM_LANGUAGE_TRANSLATIONS = {'kmr': 'Kurdî (Tirkiye)', 'tn': 'Setswana', 'tl': 'ᜆᜄᜎᜓᜄ᜔'} +CUSTOM_LANGUAGE_TRANSLATIONS = { + "kmr": "Kurdî (Tirkiye)", + "tn": "Setswana", + "tl": "ᜆᜄᜎᜓᜄ᜔", +} customize_babel_locale(CUSTOM_BABEL_LANGUAGES) -KEYWORDS_ADVENTURES = set([ - 'print_command', - 'ask_command', - 'is_command', - 'sleep_command', - 'random_command', - 'add_remove_command', - 'quotation_marks', - 'if_command', - 'in_command', - 'maths', - 'repeat_command', - 'repeat_command_2', - 'for_command', - 'and_or_command', - 'while_command', - 'elif_command', - 'clear_command' -]) +KEYWORDS_ADVENTURES = set( + [ + "print_command", + "ask_command", + "is_command", + "sleep_command", + "random_command", + "add_remove_command", + "quotation_marks", + "if_command", + "in_command", + "maths", + "repeat_command", + "repeat_command_2", + "for_command", + "and_or_command", + "while_command", + "elif_command", + "clear_command", + ] +) ADVENTURE_ORDER_PER_LEVEL = { 1: [ - 'default', - 'print_command', - 'ask_command', - 'parrot', - 'rock', - 'haunted', - 'story', - 'turtle', - 'restaurant', - 'fortune', + "default", + "print_command", + "ask_command", + "parrot", + "rock", + "haunted", + "story", + "turtle", + "restaurant", + "fortune", ], 2: [ - 'default', - 'is_command', - 'rock', - 'ask_command', - 'rock_2', - 'haunted', - 'sleep_command', - 'parrot', - 'story', - 'restaurant', - 'turtle' + "default", + "is_command", + "rock", + "ask_command", + "rock_2", + "haunted", + "sleep_command", + "parrot", + "story", + "restaurant", + "turtle", ], 3: [ - 'default', - 'random_command', - 'dice', - 'rock', - 'fortune', - 'restaurant', - 'add_remove_command', - 'parrot', - 'dishes', - 'story', - 'haunted', - 'turtle' + "default", + "random_command", + "dice", + "rock", + "fortune", + "restaurant", + "add_remove_command", + "parrot", + "dishes", + "story", + "haunted", + "turtle", ], 4: [ - 'default', - 'quotation_marks', - 'rock', - 'dice', - 'dishes', - 'parrot', - 'turtle', - 'clear_command', - 'story', - 'haunted', - 'fortune', - 'restaurant' + "default", + "quotation_marks", + "rock", + "dice", + "dishes", + "parrot", + "turtle", + "clear_command", + "story", + "haunted", + "fortune", + "restaurant", ], 5: [ - 'default', - 'if_command', - 'language', - 'dice', - 'dishes', - 'story', - 'rock', - 'parrot', - 'haunted', - 'in_command', - 'restaurant', - 'fortune', - 'pressit', - 'turtle' + "default", + "if_command", + "language", + "dice", + "dishes", + "story", + "rock", + "parrot", + "haunted", + "in_command", + "restaurant", + "fortune", + "pressit", + "turtle", ], 6: [ - 'default', - 'maths', - 'songs', - 'dice', - 'dishes', - 'turtle', - 'calculator', - 'fortune', - 'restaurant' + "default", + "maths", + "songs", + "dice", + "dishes", + "turtle", + "calculator", + "fortune", + "restaurant", ], 7: [ - 'default', - 'repeat_command', - 'story', - 'songs', - 'dishes', - 'dice', - 'repeat_command_2', - 'fortune', - 'restaurant', - 'pressit' + "default", + "repeat_command", + "story", + "songs", + "dishes", + "dice", + "repeat_command_2", + "fortune", + "restaurant", + "pressit", ], 8: [ - 'default', - 'repeat_command', - 'fortune', - 'repeat_command_2', - 'songs', - 'if_command', - 'story', - 'haunted', - 'restaurant', - 'turtle' + "default", + "repeat_command", + "fortune", + "repeat_command_2", + "songs", + "if_command", + "story", + "haunted", + "restaurant", + "turtle", ], 9: [ - 'default', - 'repeat_command', - 'rock', - 'story', - 'calculator', - 'restaurant', - 'haunted', - 'pressit', - 'turtle' + "default", + "repeat_command", + "rock", + "story", + "calculator", + "restaurant", + "haunted", + "pressit", + "turtle", ], 10: [ - 'default', - 'for_command', - 'dishes', - 'dice', - 'fortune', - 'harry_potter', - 'songs', - 'story', - 'rock', - 'calculator', - 'restaurant', + "default", + "for_command", + "dishes", + "dice", + "fortune", + "harry_potter", + "songs", + "story", + "rock", + "calculator", + "restaurant", ], 11: [ - 'default', - 'for_command', - 'years', - 'calculator', - 'songs', - 'restaurant', - 'haunted' + "default", + "for_command", + "years", + "calculator", + "songs", + "restaurant", + "haunted", ], 12: [ - 'default', - 'maths', - 'quotation_marks', - 'story', - 'fortune', - 'songs', - 'restaurant', - 'calculator', - 'piggybank', - 'secret' + "default", + "maths", + "quotation_marks", + "story", + "fortune", + "songs", + "restaurant", + "calculator", + "piggybank", + "secret", ], 13: [ - 'default', - 'and_or_command', - 'secret', - 'story', - 'rock', - 'restaurant', - 'calculator', - 'tic' + "default", + "and_or_command", + "secret", + "story", + "rock", + "restaurant", + "calculator", + "tic", ], 14: [ - 'default', - 'is_command', - 'haunted', - 'calculator', - 'piggybank', - 'quizmaster', - 'tic' + "default", + "is_command", + "haunted", + "calculator", + "piggybank", + "quizmaster", + "tic", ], 15: [ - 'default', - 'while_command', - 'restaurant', - 'story', - 'dice', - 'rock', - 'calculator', - 'tic' - ], - 16: [ - 'default', - 'random_command', - 'haunted', - 'songs', - 'language' + "default", + "while_command", + "restaurant", + "story", + "dice", + "rock", + "calculator", + "tic", ], - 17: [ - 'default', - 'print_command', - 'elif_command', - 'tic', - 'blackjack' - ], - 18: [ - 'default', - 'print_command', - 'story', - 'songs' - ] + 16: ["default", "random_command", "haunted", "songs", "language"], + 17: ["default", "print_command", "elif_command", "tic", "blackjack"], + 18: ["default", "print_command", "story", "songs"], } RESEARCH = {} -for paper in sorted(os.listdir('content/research'), - key=lambda x: int(x.split("_")[-1][:-4]), - reverse=True): +for paper in sorted( + os.listdir("content/research"), + key=lambda x: int(x.split("_")[-1][:-4]), + reverse=True, +): # An_approach_to_describing_the_semantics_of_Hedy_2022.pdf -> 2022, An # approach to describing the semantics of Hedy name = paper.replace("_", " ").split(".")[0] @@ -259,34 +261,35 @@ # load all available languages in dict # list_translations of babel does about the same, but without territories. languages = {} -if not os.path.isdir('translations'): +if not os.path.isdir("translations"): # should not be possible, but if it's moved someday, EN would still be working. - ALL_LANGUAGES['en'] = 'English' - ALL_KEYWORD_LANGUAGES['en'] = 'EN' + ALL_LANGUAGES["en"] = "English" + ALL_KEYWORD_LANGUAGES["en"] = "EN" -for folder in os.listdir('translations'): - locale_dir = os.path.join('translations', folder, 'LC_MESSAGES') +for folder in os.listdir("translations"): + locale_dir = os.path.join("translations", folder, "LC_MESSAGES") if not os.path.isdir(locale_dir): continue - if filter(lambda x: x.endswith('.mo'), os.listdir(locale_dir)): - languages[folder] = CUSTOM_LANGUAGE_TRANSLATIONS.get(folder, - static_babel_content.LANGUAGE_NAMES.get(folder, folder)) + if filter(lambda x: x.endswith(".mo"), os.listdir(locale_dir)): + languages[folder] = CUSTOM_LANGUAGE_TRANSLATIONS.get( + folder, static_babel_content.LANGUAGE_NAMES.get(folder, folder) + ) for lang in sorted(languages): ALL_LANGUAGES[lang] = languages[lang] - if os.path.exists('./grammars/keywords-' + lang + '.lark'): + if os.path.exists("./grammars/keywords-" + lang + ".lark"): ALL_KEYWORD_LANGUAGES[lang] = lang[0:2].upper() # first two characters # Load and cache all keyword yamls KEYWORDS = {} for lang in ALL_KEYWORD_LANGUAGES.keys(): - KEYWORDS[lang] = YamlFile.for_file(f'content/keywords/{lang}.yaml').to_dict() + KEYWORDS[lang] = YamlFile.for_file(f"content/keywords/{lang}.yaml").to_dict() for k, v in KEYWORDS[lang].items(): if isinstance(v, str) and "|" in v: # when we have several options, pick the first one as default # Some keys are ints, turn them into strings - KEYWORDS[lang][k] = v.split('|')[0] + KEYWORDS[lang][k] = v.split("|")[0] class StructuredDataFile: @@ -307,7 +310,7 @@ def file(self): class Commands(StructuredDataFile): def __init__(self, language): self.language = language - super().__init__(f'content/cheatsheets/{self.language}.yaml') + super().__init__(f"content/cheatsheets/{self.language}.yaml") def get_commands_for_level(self, level, keyword_lang): return deep_translate_keywords(self.file.get(int(level), {}), keyword_lang) @@ -317,18 +320,21 @@ def deep_translate_keywords(yaml, keyword_language): """Recurse through a data structure and replace keyword placeholders in any strings we encounter.""" if isinstance(yaml, str): # this is used to localize adventures linked in slides (PR 3860) - yaml = yaml.replace('/raw', f'/raw?keyword_language={keyword_language}') + yaml = yaml.replace("/raw", f"/raw?keyword_language={keyword_language}") return safe_format(yaml, **KEYWORDS.get(keyword_language)) if isinstance(yaml, list): return [deep_translate_keywords(e, keyword_language) for e in yaml] if isinstance(yaml, dict): - return {k: deep_translate_keywords(v, keyword_language) for k, v in yaml.items()} + return { + k: deep_translate_keywords(v, keyword_language) for k, v in yaml.items() + } return yaml def get_localized_name(name, keyword_lang): return safe_format(name, **KEYWORDS.get(keyword_lang)) + # Todo TB -> We don't need these anymore as we guarantee with Weblate that # each language file is there @@ -341,19 +347,24 @@ def get_commands_for_level(self, level, keyword_lang): class Adventures(StructuredDataFile): def __init__(self, language): self.language = language - super().__init__(f'content/adventures/{self.language}.yaml') + super().__init__(f"content/adventures/{self.language}.yaml") def get_adventure_keyname_name_levels(self): - return {aid: {adv['name']: list(adv['levels'].keys())} for aid, adv in self.file.get('adventures', {}).items()} + return { + aid: {adv["name"]: list(adv["levels"].keys())} + for aid, adv in self.file.get("adventures", {}).items() + } def get_adventure_names(self): - return {aid: adv['name'] for aid, adv in self.file.get('adventures', {}).items()} + return { + aid: adv["name"] for aid, adv in self.file.get("adventures", {}).items() + } def get_adventures(self, keyword_lang="en"): - return deep_translate_keywords(self.file.get('adventures'), keyword_lang) + return deep_translate_keywords(self.file.get("adventures"), keyword_lang) def has_adventures(self): - return True if self.file.get('adventures') else False + return True if self.file.get("adventures") else False class NoSuchAdventure: @@ -364,31 +375,41 @@ def get_adventure(self): class ParsonsProblem(StructuredDataFile): def __init__(self, language): self.language = language - super().__init__(f'content/parsons/{self.language}.yaml') + super().__init__(f"content/parsons/{self.language}.yaml") def get_highest_exercise_level(self, level): - return max(int(lnum) for lnum in self.file.get('levels', {}).get(level, {}).keys()) + return max( + int(lnum) for lnum in self.file.get("levels", {}).get(level, {}).keys() + ) def get_parsons_data_for_level(self, level, keyword_lang="en"): - return deep_translate_keywords(self.file.get('levels', {}).get(level, None), keyword_lang) + return deep_translate_keywords( + self.file.get("levels", {}).get(level, None), keyword_lang + ) def get_parsons_data_for_level_exercise(self, level, excercise, keyword_lang="en"): - return deep_translate_keywords(self.file.get('levels', {}).get(level, {}).get(excercise), keyword_lang) + return deep_translate_keywords( + self.file.get("levels", {}).get(level, {}).get(excercise), keyword_lang + ) class Quizzes(StructuredDataFile): def __init__(self, language): self.language = language - super().__init__(f'content/quizzes/{self.language}.yaml') + super().__init__(f"content/quizzes/{self.language}.yaml") def get_highest_question_level(self, level): - return max(int(k) for k in self.file.get('levels', {}).get(level, {})) + return max(int(k) for k in self.file.get("levels", {}).get(level, {})) def get_quiz_data_for_level(self, level, keyword_lang="en"): - return deep_translate_keywords(self.file.get('levels', {}).get(level), keyword_lang) + return deep_translate_keywords( + self.file.get("levels", {}).get(level), keyword_lang + ) def get_quiz_data_for_level_question(self, level, question, keyword_lang="en"): - return deep_translate_keywords(self.file.get('levels', {}).get(level, {}).get(question), keyword_lang) + return deep_translate_keywords( + self.file.get("levels", {}).get(level, {}).get(question), keyword_lang + ) class NoSuchQuiz: @@ -401,7 +422,7 @@ class Tutorials(StructuredDataFile): # action on server start def __init__(self, language): self.language = language - super().__init__(f'content/tutorials/{self.language}.yaml') + super().__init__(f"content/tutorials/{self.language}.yaml") def get_tutorial_for_level(self, level, keyword_lang="en"): if level not in ["intro", "teacher"]: @@ -411,7 +432,9 @@ def get_tutorial_for_level(self, level, keyword_lang="en"): def get_tutorial_for_level_step(self, level, step, keyword_lang="en"): if level not in ["intro", "teacher"]: level = int(level) - return deep_translate_keywords(self.file.get(level, {}).get('steps', {}).get(step), keyword_lang) + return deep_translate_keywords( + self.file.get(level, {}).get("steps", {}).get(step), keyword_lang + ) class NoSuchTutorial: @@ -422,10 +445,12 @@ def get_tutorial_for_level(self, level, keyword_lang): class Slides(StructuredDataFile): def __init__(self, language): self.language = language - super().__init__(f'content/slides/{self.language}.yaml') + super().__init__(f"content/slides/{self.language}.yaml") def get_slides_for_level(self, level, keyword_lang="en"): - return deep_translate_keywords(self.file.get('levels', {}).get(level), keyword_lang) + return deep_translate_keywords( + self.file.get("levels", {}).get(level), keyword_lang + ) class NoSuchSlides: diff --git a/hedy_sourcemap.py b/hedy_sourcemap.py index 8f38ada39fb..556e8a57a18 100644 --- a/hedy_sourcemap.py +++ b/hedy_sourcemap.py @@ -30,18 +30,17 @@ def __init__(self, from_line, from_column, to_line, to_column): self.to_column = to_column def __str__(self): - return f'{self.from_line}/{self.from_column}-{self.to_line}/{self.to_column}' + return f"{self.from_line}/{self.from_column}-{self.to_line}/{self.to_column}" def __repr__(self): return self.__str__() def __eq__(self, other): - return ( - self.from_line, self.from_column, - self.to_line, self.to_column - ) == ( - other.from_line, other.from_column, - other.to_line, other.to_column + return (self.from_line, self.from_column, self.to_line, self.to_column) == ( + other.from_line, + other.from_column, + other.to_line, + other.to_column, ) @@ -59,18 +58,26 @@ def __init__(self, source_range: SourceRange, code: str, error: Exception = None self.error = error def __hash__(self): - return hash(( - self.source_range.from_line, self.source_range.from_column, - self.source_range.to_line, self.source_range.to_column - )) + return hash( + ( + self.source_range.from_line, + self.source_range.from_column, + self.source_range.to_line, + self.source_range.to_column, + ) + ) def __eq__(self, other): return ( - self.source_range.from_line, self.source_range.from_column, - self.source_range.to_line, self.source_range.to_column + self.source_range.from_line, + self.source_range.from_column, + self.source_range.to_line, + self.source_range.to_column, ) == ( - other.source_range.from_line, other.source_range.from_column, - other.source_range.to_line, other.source_range.to_column + other.source_range.from_line, + other.source_range.from_column, + other.source_range.to_line, + other.source_range.to_column, ) def __ne__(self, other): @@ -78,9 +85,9 @@ def __ne__(self, other): def __str__(self): if self.error is None: - return f'{self.source_range} --- {self.code}' + return f"{self.source_range} --- {self.code}" else: - return f'{self.source_range} -- ERROR[{self.error}] CODE[{self.code}]' + return f"{self.source_range} -- ERROR[{self.error}] CODE[{self.code}]" def __repr__(self): return self.__str__() @@ -104,13 +111,11 @@ class SourceMap: skip_faulty = False exception_found_during_parsing = None - exceptions_not_to_skip = ( - exceptions.UnsupportedStringValue, - ) + exceptions_not_to_skip = (exceptions.UnsupportedStringValue,) - language = 'en' - hedy_code = '' - python_code = '' + language = "en" + hedy_code = "" + python_code = "" grammar_rules = [] def __init__(self): @@ -134,28 +139,29 @@ def set_python_output(self, python_code): python_code_mapped = list() def line_col(context, idx): - return context.count('\n', 0, idx) + 1, idx - context.rfind('\n', 0, idx) + return context.count("\n", 0, idx) + 1, idx - context.rfind("\n", 0, idx) for hedy_source_code, python_source_code in self.map.items(): - if hedy_source_code.error is not None or python_source_code.code == '': + if hedy_source_code.error is not None or python_source_code.code == "": continue start_index = python_code.find(python_source_code.code) code_char_length = len(python_source_code.code) for i in range(python_code_mapped.count(python_source_code.code)): - start_index = python_code.find(python_source_code.code, start_index+code_char_length) - start_index = max(0, start_index) # not found (-1) means that start_index = 0 + start_index = python_code.find( + python_source_code.code, start_index + code_char_length + ) + start_index = max( + 0, start_index + ) # not found (-1) means that start_index = 0 end_index = start_index + code_char_length start_line, start_column = line_col(python_code, start_index) end_line, end_column = line_col(python_code, end_index) python_source_code.source_range = SourceRange( - start_line, - start_column, - end_line, - end_column + start_line, start_column, end_line, end_column ) python_code_mapped.append(python_source_code.code) @@ -163,15 +169,23 @@ def line_col(context, idx): def get_grammar_rules(self): script_dir = path.abspath(path.dirname(__file__)) - with open(path.join(script_dir, "grammars", "level1.lark"), "r", encoding="utf-8") as file: + with open( + path.join(script_dir, "grammars", "level1.lark"), "r", encoding="utf-8" + ) as file: grammar_text = file.read() for i in range(2, 19): - with open(path.join(script_dir, "grammars", f'level{i}-Additions.lark'), "r", encoding="utf-8") as file: - grammar_text += '\n' + file.read() + with open( + path.join(script_dir, "grammars", f"level{i}-Additions.lark"), + "r", + encoding="utf-8", + ) as file: + grammar_text += "\n" + file.read() self.grammar_rules = re.findall(r"(\w+):", grammar_text) - self.grammar_rules = [rule for rule in self.grammar_rules if 'text' not in rule] # exclude text from mapping + self.grammar_rules = [ + rule for rule in self.grammar_rules if "text" not in rule + ] # exclude text from mapping self.grammar_rules = list(set(self.grammar_rules)) # remove duplicates def add_source(self, hedy_code: SourceCode, python_code: SourceCode): @@ -180,9 +194,9 @@ def add_source(self, hedy_code: SourceCode, python_code: SourceCode): def clear(self): self.map.clear() self.level = 0 - self.language = 'en' - self.hedy_code = '' - self.python_code = '' + self.language = "en" + self.hedy_code = "" + self.python_code = "" def get_result(self): response_map = dict() @@ -190,19 +204,19 @@ def get_result(self): for hedy_source_code, python_source_code in self.map.items(): response_map[index] = { - 'hedy_range': { - 'from_line': hedy_source_code.source_range.from_line, - 'from_column': hedy_source_code.source_range.from_column, - 'to_line': hedy_source_code.source_range.to_line, - 'to_column': hedy_source_code.source_range.to_column, + "hedy_range": { + "from_line": hedy_source_code.source_range.from_line, + "from_column": hedy_source_code.source_range.from_column, + "to_line": hedy_source_code.source_range.to_line, + "to_column": hedy_source_code.source_range.to_column, }, - 'python_range': { - 'from_line': python_source_code.source_range.from_line, - 'from_column': python_source_code.source_range.from_column, - 'to_line': python_source_code.source_range.to_line, - 'to_column': python_source_code.source_range.to_column, + "python_range": { + "from_line": python_source_code.source_range.from_line, + "from_column": python_source_code.source_range.from_column, + "to_line": python_source_code.source_range.to_line, + "to_column": python_source_code.source_range.to_column, }, - 'error': hedy_source_code.error, + "error": hedy_source_code.error, } index += 1 @@ -213,7 +227,9 @@ def get_compressed_mapping(self): response_map = dict() for hedy_source_code, python_source_code in self.map.items(): - response_map[str(hedy_source_code.source_range)] = str(python_source_code.source_range) + response_map[str(hedy_source_code.source_range)] = str( + python_source_code.source_range + ) return response_map @@ -224,15 +240,15 @@ def get_error_from_hedy_source_range(self, hedy_range: SourceRange) -> Exception def print_source_map(self, d, indent=0): for key, value in d.items(): - print('\t' * indent + str(key) + ':') + print("\t" * indent + str(key) + ":") if isinstance(value, dict): self.print_source_map(value, indent + 1) else: code_lines = str(value).splitlines() - code_lines = ['\t' * (indent + 1) + str(x) for x in code_lines] + code_lines = ["\t" * (indent + 1) + str(x) for x in code_lines] code = "\n".join(code_lines) print(code) - print('') + print("") def __str__(self): self.print_source_map(self.map) @@ -240,16 +256,18 @@ def __str__(self): def source_map_rule(source_map: SourceMap): - """ A decorator function that should decorator the transformer method (grammar rule) - the decorator adds the hedy code & python code to the map when the transformer method (grammar rule) is used + """A decorator function that should decorator the transformer method (grammar rule) + the decorator adds the hedy code & python code to the map when the transformer method (grammar rule) is used """ def decorator(function): def wrapper(*args, **kwargs): meta = args[1] - hedy_code_input = source_map.hedy_code[meta.start_pos:meta.end_pos] - hedy_code_input = hedy_code_input.replace('#ENDBLOCK', '') # ENDBLOCK is not part of the Hedy code, remove + hedy_code_input = source_map.hedy_code[meta.start_pos : meta.end_pos] + hedy_code_input = hedy_code_input.replace( + "#ENDBLOCK", "" + ) # ENDBLOCK is not part of the Hedy code, remove error = None if not source_map.skip_faulty: @@ -264,31 +282,34 @@ def wrapper(*args, **kwargs): if ( # if a Lark tree is returned - isinstance(generated_python, Tree) or + isinstance(generated_python, Tree) + or # if a Lark tree is returned as a string, we check with regex bool(re.match(r".*Tree\(.*Token\(.*\).*\).*", generated_python)) ): - raise Exception('Can not map a Lark tree, only strings') + raise Exception("Can not map a Lark tree, only strings") except Exception as e: # If an exception is found, we set the Python code to pass (null operator) # we also map the error - generated_python = 'pass' + generated_python = "pass" error = e hedy_code = SourceCode( SourceRange( - meta.container_line, meta.container_column, - meta.container_end_line, meta.container_end_column + meta.container_line, + meta.container_column, + meta.container_end_line, + meta.container_end_column, ), hedy_code_input, - error=error + error=error, ) python_code = SourceCode( # We don't know now, set_python_output will set the ranges later SourceRange(None, None, None, None), - generated_python + generated_python, ) source_map.add_source(hedy_code, python_code) @@ -300,11 +321,11 @@ def wrapper(*args, **kwargs): def source_map_transformer(source_map: SourceMap): - """ A decorator function that should decorate a transformer class + """A decorator function that should decorate a transformer class - This is used for convenience, instead of adding source_map_rule to all methods, - source_map_transformer needs only to be added to the transformer class. - This decorator add source_map_rule to all appropriate methods. + This is used for convenience, instead of adding source_map_rule to all methods, + source_map_transformer needs only to be added to the transformer class. + This decorator add source_map_rule to all appropriate methods. """ def decorate(cls): diff --git a/hedy_translation.py b/hedy_translation.py index 1c39c447d02..74b3d987ad8 100644 --- a/hedy_translation.py +++ b/hedy_translation.py @@ -39,7 +39,9 @@ def all_keywords_to_dict(): commands = keywords_to_dict_single_choice(lang) keyword_dict[lang] = commands - all_translations = {k: [v.get(k, k) for v in keyword_dict.values()] for k in keyword_dict["en"]} + all_translations = { + k: [v.get(k, k) for v in keyword_dict.values()] for k in keyword_dict["en"] + } return all_translations @@ -73,7 +75,11 @@ def translate_keywords(input_string_, from_lang="en", to_lang="nl", level=1): """ "Return code with keywords translated to language of choice in level of choice""" try: processed_input = hedy.process_input_string( - input_string_, level, from_lang, escape_backslashes=False, preprocess_ifs_enabled=False + input_string_, + level, + from_lang, + escape_backslashes=False, + preprocess_ifs_enabled=False, ) hedy.source_map.clear() @@ -87,7 +93,9 @@ def translate_keywords(input_string_, from_lang="en", to_lang="nl", level=1): translator = Translator(processed_input) translator.visit(program_root) - ordered_rules = reversed(sorted(translator.rules, key=operator.attrgetter("line", "start"))) + ordered_rules = reversed( + sorted(translator.rules, key=operator.attrgetter("line", "start")) + ) # FH Feb 2022 TODO trees containing invalid nodes are happily translated, # should be stopped here! @@ -113,7 +121,7 @@ def translate_keywords(input_string_, from_lang="en", to_lang="nl", level=1): def replace_line(lines, index, line): before = "\n".join(lines[0:index]) - after = "\n".join(lines[index + 1:]) + after = "\n".join(lines[index + 1 :]) if len(before) > 0: before = before + "\n" if len(after) > 0: @@ -123,8 +131,8 @@ def replace_line(lines, index, line): def replace_token_in_line(line, rule, original, target): """Replaces a token in a line from the user input with its translated equivalent""" - before = "" if rule.start == 0 else line[0: rule.start] - after = "" if rule.end == len(line) - 1 else line[rule.end + 1:] + before = "" if rule.start == 0 else line[0 : rule.start] + after = "" if rule.end == len(line) - 1 else line[rule.end + 1 :] # Note that we need to replace the target value in the original value because some # grammar rules have ambiguous length and value, e.g. _COMMA: _SPACES* # (latin_comma | arabic_comma) _SPACES* @@ -148,10 +156,18 @@ def find_command_keywords( } -def find_keyword_in_rules(rules, keyword, start_line, end_line, start_column, end_column): +def find_keyword_in_rules( + rules, keyword, start_line, end_line, start_column, end_column +): for rule in rules: - if rule.keyword == keyword and rule.line == start_line and rule.start >= start_column: - if rule.line < end_line or (rule.line == end_line and rule.end <= end_column): + if ( + rule.keyword == keyword + and rule.line == start_line + and rule.start >= start_column + ): + if rule.line < end_line or ( + rule.line == end_line and rule.end <= end_column + ): return rule.value return None @@ -218,19 +234,25 @@ def turn(self, tree): def left(self, tree): token = tree.children[0] - rule = Rule("left", token.line, token.column - 1, token.end_column - 2, token.value) + rule = Rule( + "left", token.line, token.column - 1, token.end_column - 2, token.value + ) self.rules.append(rule) def right(self, tree): token = tree.children[0] - rule = Rule("right", token.line, token.column - 1, token.end_column - 2, token.value) + rule = Rule( + "right", token.line, token.column - 1, token.end_column - 2, token.value + ) self.rules.append(rule) def assign_list(self, tree): self.add_rule("_IS", "is", tree) commas = self.get_keyword_tokens("_COMMA", tree) for comma in commas: - rule = Rule("comma", comma.line, comma.column - 1, comma.end_column - 2, comma.value) + rule = Rule( + "comma", comma.line, comma.column - 1, comma.end_column - 2, comma.value + ) self.rules.append(rule) def assign(self, tree): @@ -249,7 +271,9 @@ def remove(self, tree): def random(self, tree): token = tree.children[0] - rule = Rule("random", token.line, token.column - 1, token.end_column - 2, token.value) + rule = Rule( + "random", token.line, token.column - 1, token.end_column - 2, token.value + ) self.rules.append(rule) def ifs(self, tree): @@ -324,7 +348,11 @@ def add_rule(self, token_name, token_keyword, tree): token = self.get_keyword_token(token_name, tree) if token: rule = Rule( - token_keyword, token.line, token.column - 1, token.end_column - 2, token.value + token_keyword, + token.line, + token.column - 1, + token.end_column - 2, + token.value, ) self.rules.append(rule) diff --git a/hedyweb.py b/hedyweb.py index 673fe237736..3ccbe484e97 100644 --- a/hedyweb.py +++ b/hedyweb.py @@ -9,14 +9,14 @@ class AchievementTranslations: def __init__(self): self.data = {} - translations = glob.glob('content/achievements/*.yaml') + translations = glob.glob("content/achievements/*.yaml") for trans_file in translations: lang = path.splitext(path.basename(trans_file))[0] self.data[lang] = YamlFile.for_file(trans_file) def get_translations(self, language): - d = collections.defaultdict(lambda: 'Unknown Exception') - d.update(**self.data.get('en', {})) + d = collections.defaultdict(lambda: "Unknown Exception") + d.update(**self.data.get("en", {})) d.update(**self.data.get(language, {})) return d @@ -24,10 +24,10 @@ def get_translations(self, language): class PageTranslations: def __init__(self, page): self.data = {} - if page in ['start', 'join', 'learn-more', 'for-teachers']: - translations = glob.glob('content/pages/*.yaml') + if page in ["start", "join", "learn-more", "for-teachers"]: + translations = glob.glob("content/pages/*.yaml") else: - translations = glob.glob('content/pages/' + page + '/*.yaml') + translations = glob.glob("content/pages/" + page + "/*.yaml") for file in translations: lang = path.splitext(path.basename(file))[0] self.data[lang] = YamlFile.for_file(file) @@ -37,7 +37,7 @@ def exists(self): return len(self.data) > 0 def get_page_translations(self, language): - d = collections.defaultdict(lambda: '') - d.update(**self.data.get('en', {})) + d = collections.defaultdict(lambda: "") + d.update(**self.data.get("en", {})) d.update(**self.data.get(language, {})) return d diff --git a/highlighting/definition.py b/highlighting/definition.py index 30992ab7dc0..6b749c0fe97 100644 --- a/highlighting/definition.py +++ b/highlighting/definition.py @@ -1,22 +1,22 @@ # This file defines the special regexes # list of symbols recognized as characters (with non-Latin characters) -CHARACTER = '[\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}_\\p{Mn}\\p{Mc}\\p{Nd}\\p{Pc}·]' +CHARACTER = "[\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}_\\p{Mn}\\p{Mc}\\p{Nd}\\p{Pc}·]" # definition of word -WORD = '(' + CHARACTER + "+)" +WORD = "(" + CHARACTER + "+)" # space SPACE = "( +)" # beginning and end of one line, including space -START_LINE = '(^ *)' -END_LINE = '( *$)' +START_LINE = "(^ *)" +END_LINE = "( *$)" # beginning and end of words -START_WORD = '(^| )' -END_WORD = '(?!' + CHARACTER + ')' +START_WORD = "(^| )" +END_WORD = "(?!" + CHARACTER + ")" -DIGIT = '[__DIGIT__]' +DIGIT = "[__DIGIT__]" TRANSLATE_WORDS = [ "define", @@ -68,7 +68,7 @@ "length", "comma", "pressed", - "clear" + "clear", ] @@ -76,10 +76,10 @@ def get_translated_keyword(word, withoutGroup=False): - """ Function that allows to add double underscores around the keywords to be translated. - The "__" are added before and after only if the keyword belongs to the list. + """Function that allows to add double underscores around the keywords to be translated. + The "__" are added before and after only if the keyword belongs to the list. - - withoutGroup : bool, Add parentheses for make a group or not + - withoutGroup : bool, Add parentheses for make a group or not """ if withoutGroup: if word in TRANSLATE_WORDS: diff --git a/highlighting/generate-rules-highlighting.py b/highlighting/generate-rules-highlighting.py index 7765507a1a4..4727a3f54cd 100644 --- a/highlighting/generate-rules-highlighting.py +++ b/highlighting/generate-rules-highlighting.py @@ -4,6 +4,7 @@ import yaml from definition import TRANSLATE_WORDS + # import rules from files from rules_automaton import rule_level1, rule_level2, rule_level3, rule_level4 from rules_list import rule_all @@ -13,8 +14,8 @@ OUTPUT_PATH_TRANSLATION = "highlighting/highlighting-trad.json" # Files containing translations of keywords -KEYWORDS_PATH = 'content/keywords/' -KEYWORDS_PATTERN = '(\\w+).yaml$' +KEYWORDS_PATH = "content/keywords/" +KEYWORDS_PATTERN = "(\\w+).yaml$" # Functions that collect all the rules, for all levels, of a given language @@ -25,7 +26,7 @@ def main(): print("Generation of translations.....................", end="") language_keywords = get_translations(KEYWORDS_PATH, KEYWORDS_PATTERN) # Saving the rules in the corresponding file - with open(OUTPUT_PATH_TRANSLATION, "w", encoding='utf8') as file_lang: + with open(OUTPUT_PATH_TRANSLATION, "w", encoding="utf8") as file_lang: file_lang.write(json.dumps(language_keywords, indent=4, ensure_ascii=False)) print(" Done !") @@ -37,7 +38,7 @@ def main(): validate_ruleset(levels) # Saving the rules in the corresponding file - with open(OUTPUT_PATH_HIGHLIGHT, "w", encoding='utf8') as file_syntax: + with open(OUTPUT_PATH_HIGHLIGHT, "w", encoding="utf8") as file_syntax: file_syntax.write(json.dumps(levels, indent=4, ensure_ascii=False)) print(" Done !") @@ -45,24 +46,24 @@ def main(): def generate_rules(): return [ - {'name': 'level1', 'rules': rule_level1()}, - {'name': 'level2', 'rules': rule_level2()}, - {'name': 'level3', 'rules': rule_level3()}, - {'name': 'level4', 'rules': rule_level4()}, - {'name': 'level5', 'rules': rule_all(5)}, - {'name': 'level6', 'rules': rule_all(6)}, - {'name': 'level7', 'rules': rule_all(7)}, - {'name': 'level8', 'rules': rule_all(8)}, - {'name': 'level9', 'rules': rule_all(9)}, - {'name': 'level10', 'rules': rule_all(10)}, - {'name': 'level11', 'rules': rule_all(11)}, - {'name': 'level12', 'rules': rule_all(12)}, - {'name': 'level13', 'rules': rule_all(13)}, - {'name': 'level14', 'rules': rule_all(14)}, - {'name': 'level15', 'rules': rule_all(15)}, - {'name': 'level16', 'rules': rule_all(16)}, - {'name': 'level17', 'rules': rule_all(17)}, - {'name': 'level18', 'rules': rule_all(18)}, + {"name": "level1", "rules": rule_level1()}, + {"name": "level2", "rules": rule_level2()}, + {"name": "level3", "rules": rule_level3()}, + {"name": "level4", "rules": rule_level4()}, + {"name": "level5", "rules": rule_all(5)}, + {"name": "level6", "rules": rule_all(6)}, + {"name": "level7", "rules": rule_all(7)}, + {"name": "level8", "rules": rule_all(8)}, + {"name": "level9", "rules": rule_all(9)}, + {"name": "level10", "rules": rule_all(10)}, + {"name": "level11", "rules": rule_all(11)}, + {"name": "level12", "rules": rule_all(12)}, + {"name": "level13", "rules": rule_all(13)}, + {"name": "level14", "rules": rule_all(14)}, + {"name": "level15", "rules": rule_all(15)}, + {"name": "level16", "rules": rule_all(16)}, + {"name": "level17", "rules": rule_all(17)}, + {"name": "level18", "rules": rule_all(18)}, ] @@ -70,33 +71,36 @@ def validate_ruleset(levels): """Confirm that the generated syntax highlighting rules are valid, throw an error if not.""" errors = 0 for level in levels: - for state, rules in level['rules'].items(): + for state, rules in level["rules"].items(): for rule in rules: - r = re.compile(rule['regex']) + r = re.compile(rule["regex"]) if r.groups == 0: if not isinstance(rule["token"], str): raise ValueError( - f"In {level['name']}, state \'{state}\': if regex has no groups, token must be a string." - f"In this rule.\n{rule}") + f"In {level['name']}, state '{state}': if regex has no groups, token must be a string." + f"In this rule.\n{rule}" + ) errors += 1 else: if not isinstance(rule["token"], list): raise ValueError( - f"In {level['name']}, state \'{state}\': if regex has groups, token must be a list." - f"In this rule.\n{rule}") + f"In {level['name']}, state '{state}': if regex has groups, token must be a list." + f"In this rule.\n{rule}" + ) errors += 1 else: if r.groups != len(rule["token"]): raise ValueError( - f"In {level['name']}, state \'{state}\': number of groups in the regex is different" - f"from the number of tokens. In this rule.\n{rule}") + f"In {level['name']}, state '{state}': number of groups in the regex is different" + f"from the number of tokens. In this rule.\n{rule}" + ) errors += 1 if errors > 0: - raise RuntimeError(f'{errors} rules are invalid') + raise RuntimeError(f"{errors} rules are invalid") def get_yaml_content(file_name): @@ -113,10 +117,10 @@ def get_yaml_content(file_name): """ try: - with open(file_name, newline="", encoding='utf-8') as keywords_file: + with open(file_name, newline="", encoding="utf-8") as keywords_file: yaml_file = yaml.safe_load(keywords_file) except Exception as e: - raise RuntimeError(f'Unable to read file {file_name}') from e + raise RuntimeError(f"Unable to read file {file_name}") from e commandes = {} for k in yaml_file: commandes[str(k)] = str(yaml_file[k]) @@ -142,7 +146,6 @@ def get_commands(language_code, keywords, keywords_ref, translate_words): word = keywords[keyword] if keyword in translate_words: - # special case for arabic 'underscore' if language_code == "ar": ch = "\u0640*" @@ -171,9 +174,9 @@ def get_digits(keywords, keywords_ref): """ digits = [] - for d in '0123456789': + for d in "0123456789": # Each digit is keyed as d0, d1, d2, ... - key = f'd{d}' + key = f"d{d}" if keywords_ref[key] not in digits: digits.append(keywords_ref[key]) if keywords[key] not in digits: @@ -193,16 +196,19 @@ def get_translations(KEYWORDS_PATH, KEYWORDS_PATTERN): # Only check *.yaml files if m := re.search(KEYWORDS_PATTERN, language_file): language_code = m.group(1) - tmp[language_code] = get_yaml_content(os.path.join(KEYWORDS_PATH, language_file)) + tmp[language_code] = get_yaml_content( + os.path.join(KEYWORDS_PATH, language_file) + ) # english is ref reference = tmp["en"] result = {} for language_code in sorted(tmp.keys()): - # KEYWORDS - result[language_code] = get_commands(language_code, tmp[language_code], reference, TRANSLATE_WORDS) + result[language_code] = get_commands( + language_code, tmp[language_code], reference, TRANSLATE_WORDS + ) # DIGITS result[language_code]["DIGIT"] = get_digits(tmp[language_code], reference) @@ -210,5 +216,5 @@ def get_translations(KEYWORDS_PATH, KEYWORDS_PATTERN): return result -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/highlighting/list_keywords.py b/highlighting/list_keywords.py index 849cf9afe52..328b75c24e9 100644 --- a/highlighting/list_keywords.py +++ b/highlighting/list_keywords.py @@ -1,17 +1,20 @@ from definition import SPACE, START_WORD, get_translated_keyword # These keywords will be highlighted a different color -SECONDARY_COLOR_WORDS = ['define', 'def', 'call'] +SECONDARY_COLOR_WORDS = ["define", "def", "call"] # extra rules for ask ask_after_is = { - "regex": START_WORD + get_translated_keyword("is") + SPACE + get_translated_keyword("ask"), - "token": ["text", "keyword", "text", "keyword"] + "regex": START_WORD + + get_translated_keyword("is") + + SPACE + + get_translated_keyword("ask"), + "token": ["text", "keyword", "text", "keyword"], } ask_after_equal = { "regex": "(=)" + SPACE + get_translated_keyword("ask"), - "token": ["keyword", "text", "keyword"] + "token": ["keyword", "text", "keyword"], } @@ -44,168 +47,682 @@ LEVELS = { 4: { # not used - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "color"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "color", + ], "no_space": ["comma"], "space_before": ["print", "sleep", "forward", "turn", "random"], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["clear"], "number": False, "number_with_decimal": False, - "extra_rules": [ask_after_is] + "extra_rules": [ask_after_is], }, 5: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "else", "color"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "else", + "color", + ], "no_space": ["comma"], "space_before": ["print", "sleep", "forward", "turn", "random"], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": False, "number_with_decimal": False, - "extra_rules": [ask_after_is] + "extra_rules": [ask_after_is], }, 6: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "else", "color"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "else", + "color", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+"], "space_before": ["print", "sleep", "forward", "turn", "random"], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": False, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 7: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "else", "repeat", "times", "color"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "else", + "repeat", + "times", + "color", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+"], "space_before": ["print", "sleep", "forward", "turn", "random"], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": False, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 8: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "repeat", "color"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "repeat", + "color", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+"], - "space_before": ["print", "sleep", "forward", "turn", "random", "else", "times"], + "space_before": [ + "print", + "sleep", + "forward", + "turn", + "random", + "else", + "times", + ], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": False, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 9: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "repeat", "color"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "repeat", + "color", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+"], - "space_before": ["print", "sleep", "forward", "turn", "random", "else", "times"], + "space_before": [ + "print", + "sleep", + "forward", + "turn", + "random", + "else", + "times", + ], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": False, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 10: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "repeat", "for", "color"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "repeat", + "for", + "color", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+"], - "space_before": ["print", "sleep", "forward", "turn", "random", "else", "times"], + "space_before": [ + "print", + "sleep", + "forward", + "turn", + "random", + "else", + "times", + ], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": False, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 11: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "for", "range", "to", "repeat", "color"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "for", + "range", + "to", + "repeat", + "color", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+"], - "space_before": ["print", "sleep", "forward", "turn", "random", "else", "times"], + "space_before": [ + "print", + "sleep", + "forward", + "turn", + "random", + "else", + "times", + ], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": False, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 12: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "for", "range", "to", "repeat", "color", "define", "call", "with", "return"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "for", + "range", + "to", + "repeat", + "color", + "define", + "call", + "with", + "return", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+"], - "space_before": ["print", "sleep", "forward", "turn", "random", "else", "times"], + "space_before": [ + "print", + "sleep", + "forward", + "turn", + "random", + "else", + "times", + ], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": True, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 13: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "for", "range", "to", "and", "or", "repeat", "color", "define", "call", "with", "return"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "for", + "range", + "to", + "and", + "or", + "repeat", + "color", + "define", + "call", + "with", + "return", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+"], - "space_before": ["print", "sleep", "forward", "turn", "random", "else", "times"], + "space_before": [ + "print", + "sleep", + "forward", + "turn", + "random", + "else", + "times", + ], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": True, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 14: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "for", "range", "to", "and", "or", "else", "repeat", "color", "define", "call", "with", "return"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "for", + "range", + "to", + "and", + "or", + "else", + "repeat", + "color", + "define", + "call", + "with", + "return", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+", "<", ">", "!"], "space_before": ["print", "sleep", "forward", "turn", "random", "times"], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": True, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 15: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "for", "range", "to", "and", "or", "while", "repeat", "color", "define", "call", "with", "return"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "for", + "range", + "to", + "and", + "or", + "while", + "repeat", + "color", + "define", + "call", + "with", + "return", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+", "<", ">", "!"], - "space_before": ["print", "sleep", "forward", "turn", "random", "else", "times"], + "space_before": [ + "print", + "sleep", + "forward", + "turn", + "random", + "else", + "times", + ], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": True, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 16: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "else", "for", "range", "to", "and", "or", "while", "repeat", "color", "define", "call", "with", "return"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "else", + "for", + "range", + "to", + "and", + "or", + "while", + "repeat", + "color", + "define", + "call", + "with", + "return", + ], "no_space": ["comma", "-", "=", "/", "\\*", "\\+", "<", ">", "!", "\\[", "\\]"], "space_before": ["print", "sleep", "forward", "turn", "random", "times"], "space_after": [], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": True, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 17: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "else", "for", "range", "to", "and", "or", "while", "repeat", "color", "define", "call", "with", "return"], - "no_space": ["comma", "-", "=", "/", "\\*", "\\+", "<", ">", "!", "\\[", "\\]", ":"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "else", + "for", + "range", + "to", + "and", + "or", + "while", + "repeat", + "color", + "define", + "call", + "with", + "return", + ], + "no_space": [ + "comma", + "-", + "=", + "/", + "\\*", + "\\+", + "<", + ">", + "!", + "\\[", + "\\]", + ":", + ], "space_before": ["print", "sleep", "forward", "turn", "random", "times"], "space_after": ["elif"], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": True, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, 18: { - "space_before_and_after": ["is", "at", "add", "to_list", "remove", "from", "in", "not_in", "if", "else", "for", "range", "to", "and", "or", "while", "input", "repeat", "color", "def", "return"], - "no_space": ["comma", "-", "=", "/", "\\*", "\\+", "<", ">", "!", "\\[", "\\]", ":", "\\(", "\\)"], + "space_before_and_after": [ + "is", + "at", + "add", + "to_list", + "remove", + "from", + "in", + "not_in", + "if", + "else", + "for", + "range", + "to", + "and", + "or", + "while", + "input", + "repeat", + "color", + "def", + "return", + ], + "no_space": [ + "comma", + "-", + "=", + "/", + "\\*", + "\\+", + "<", + ">", + "!", + "\\[", + "\\]", + ":", + "\\(", + "\\)", + ], "space_before": ["print", "sleep", "forward", "turn", "random", "times"], "space_after": ["elif"], - "constant": ["black", "blue", "brown", "gray", "green", "orange", "pink", "purple", "red", "white", "yellow"], + "constant": [ + "black", + "blue", + "brown", + "gray", + "green", + "orange", + "pink", + "purple", + "red", + "white", + "yellow", + ], "event": ["pressed", "clear"], "number": True, "number_with_decimal": True, - "extra_rules": [ask_after_is, ask_after_equal] + "extra_rules": [ask_after_is, ask_after_equal], }, } diff --git a/highlighting/rules_automaton.py b/highlighting/rules_automaton.py index 0627d61261b..27de1ab2db9 100644 --- a/highlighting/rules_automaton.py +++ b/highlighting/rules_automaton.py @@ -1,475 +1,678 @@ -from definition import (END_WORD, SPACE, START_LINE, START_WORD, - TOKEN_CONSTANT, WORD, get_translated_keyword) +from definition import ( + END_WORD, + SPACE, + START_LINE, + START_WORD, + TOKEN_CONSTANT, + WORD, + get_translated_keyword, +) # In the first levels, the strings are not yet well defined, # so we have to color them with respect to what is around, # so we use particular functions def rule_level1(): - return add_extra_rule({ - "start": [{ - 'regex': START_LINE + get_translated_keyword("ask"), - 'token': ["text", 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("print"), - 'token': ["text", 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("echo"), - 'token': ["text", 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("forward"), - 'token': ["text", 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("turn"), - 'token': ["text", 'keyword'], - 'next': 'direction', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("color"), - 'token': ["text", 'keyword'], - 'next': 'color', - 'unicode': True - }], - "value": [], - "color": [{ - 'regex': "(" + - get_translated_keyword("black", True) + "|" + - get_translated_keyword("gray", True) + "|" + - get_translated_keyword("white", True) + "|" + - get_translated_keyword("green", True) + "|" + - get_translated_keyword("blue", True) + "|" + - get_translated_keyword("purple", True) + "|" + - get_translated_keyword("brown", True) + "|" + - get_translated_keyword("pink", True) + "|" + - get_translated_keyword("red", True) + "|" + - get_translated_keyword("orange", True) + "|" + - get_translated_keyword("yellow", True) + - ")", - 'token': [TOKEN_CONSTANT], - 'unicode': True - }], - "direction": [{ - 'regex': "(" + - get_translated_keyword("right", True) + "|" + - get_translated_keyword("left", True) + - ")", - 'token': [TOKEN_CONSTANT], - 'unicode': True - }] - }) + return add_extra_rule( + { + "start": [ + { + "regex": START_LINE + get_translated_keyword("ask"), + "token": ["text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("print"), + "token": ["text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("echo"), + "token": ["text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("forward"), + "token": ["text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("turn"), + "token": ["text", "keyword"], + "next": "direction", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("color"), + "token": ["text", "keyword"], + "next": "color", + "unicode": True, + }, + ], + "value": [], + "color": [ + { + "regex": "(" + + get_translated_keyword("black", True) + + "|" + + get_translated_keyword("gray", True) + + "|" + + get_translated_keyword("white", True) + + "|" + + get_translated_keyword("green", True) + + "|" + + get_translated_keyword("blue", True) + + "|" + + get_translated_keyword("purple", True) + + "|" + + get_translated_keyword("brown", True) + + "|" + + get_translated_keyword("pink", True) + + "|" + + get_translated_keyword("red", True) + + "|" + + get_translated_keyword("orange", True) + + "|" + + get_translated_keyword("yellow", True) + + ")", + "token": [TOKEN_CONSTANT], + "unicode": True, + } + ], + "direction": [ + { + "regex": "(" + + get_translated_keyword("right", True) + + "|" + + get_translated_keyword("left", True) + + ")", + "token": [TOKEN_CONSTANT], + "unicode": True, + } + ], + } + ) def rule_level2(): - return add_extra_rule({ - "start": [{ - 'regex': START_LINE + get_translated_keyword("print"), - 'token': ["text", 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + WORD + SPACE + get_translated_keyword("is") + SPACE + get_translated_keyword("ask"), - 'token': ["text", "text", "text", 'keyword', "text", "keyword"], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + WORD + SPACE + get_translated_keyword("is"), - 'token': ["text", "text", "text", 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("sleep"), - 'token': ["text", 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("forward"), - 'token': ["text", 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("turn"), - 'token': ["text", 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("color"), - 'token': ["text", 'keyword'], - 'next': 'value', - 'unicode': True - }], - "value": [{ - 'regex': "(" + - get_translated_keyword("black", True) + "|" + - get_translated_keyword("blue", True) + "|" + - get_translated_keyword("brown", True) + "|" + - get_translated_keyword("gray", True) + "|" + - get_translated_keyword("green", True) + "|" + - get_translated_keyword("orange", True) + "|" + - get_translated_keyword("pink", True) + "|" + - get_translated_keyword("purple", True) + "|" + - get_translated_keyword("red", True) + "|" + - get_translated_keyword("white", True) + "|" + - get_translated_keyword("yellow", True) + - ")", - 'token': [TOKEN_CONSTANT], - 'unicode': True - }] - }) + return add_extra_rule( + { + "start": [ + { + "regex": START_LINE + get_translated_keyword("print"), + "token": ["text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + + WORD + + SPACE + + get_translated_keyword("is") + + SPACE + + get_translated_keyword("ask"), + "token": ["text", "text", "text", "keyword", "text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + WORD + SPACE + get_translated_keyword("is"), + "token": ["text", "text", "text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("sleep"), + "token": ["text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("forward"), + "token": ["text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("turn"), + "token": ["text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("color"), + "token": ["text", "keyword"], + "next": "value", + "unicode": True, + }, + ], + "value": [ + { + "regex": "(" + + get_translated_keyword("black", True) + + "|" + + get_translated_keyword("blue", True) + + "|" + + get_translated_keyword("brown", True) + + "|" + + get_translated_keyword("gray", True) + + "|" + + get_translated_keyword("green", True) + + "|" + + get_translated_keyword("orange", True) + + "|" + + get_translated_keyword("pink", True) + + "|" + + get_translated_keyword("purple", True) + + "|" + + get_translated_keyword("red", True) + + "|" + + get_translated_keyword("white", True) + + "|" + + get_translated_keyword("yellow", True) + + ")", + "token": [TOKEN_CONSTANT], + "unicode": True, + } + ], + } + ) def rule_level3(): - return add_extra_rule({"start": [{ - 'regex': START_LINE + WORD + SPACE + get_translated_keyword("is") + "( *)" + get_translated_keyword("ask"), - 'token': ["text", 'text', 'text', 'keyword', 'text', 'keyword'], - 'next': 'valueExpr', - 'unicode': True - }, { - 'regex': START_LINE + WORD + SPACE + get_translated_keyword("is"), - 'token': ["text", 'text', 'text', 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("print"), - 'token': ['text', 'keyword'], - 'next': 'valueExpr', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("turn"), - 'token': ['text', 'keyword'], - 'next': 'valueExpr', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("sleep"), - 'token': ['text', 'keyword'], - 'next': 'valueExpr', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("forward"), - 'token': ['text', 'keyword'], - 'next': 'valueExpr', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("add"), - 'token': ["text", 'keyword'], - 'next': 'valAdd', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("remove"), - 'token': ["text", 'keyword'], - 'next': 'valRemove', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("color"), - 'token': ["text", 'keyword'], - 'next': 'value', - 'unicode': True - }], - "value": [{ - 'regex': START_WORD + get_translated_keyword("at") + SPACE + get_translated_keyword("random"), - 'token': ['text', 'keyword', 'keyword', 'keyword'], - 'unicode': True - }, { - 'regex': START_WORD + get_translated_keyword("at") + END_WORD, - 'token': ['text', 'keyword'], - 'unicode': True - }, { - 'regex': get_translated_keyword("comma"), - 'token': ['keyword'], - 'unicode': True - }, { - 'regex': "(" + - get_translated_keyword("black", True) + "|" + - get_translated_keyword("blue", True) + "|" + - get_translated_keyword("brown", True) + "|" + - get_translated_keyword("gray", True) + "|" + - get_translated_keyword("green", True) + "|" + - get_translated_keyword("orange", True) + "|" + - get_translated_keyword("pink", True) + "|" + - get_translated_keyword("purple", True) + "|" + - get_translated_keyword("red", True) + "|" + - get_translated_keyword("white", True) + "|" + - get_translated_keyword("yellow", True) + - ")", - 'token': [TOKEN_CONSTANT], - 'unicode': True - }], - "valueExpr": [{ - 'regex': START_WORD + get_translated_keyword("at") + SPACE + get_translated_keyword("random"), - 'token': ['text', 'keyword', 'keyword', 'keyword'], - 'unicode': True - }, { - 'regex': START_WORD + get_translated_keyword("at") + END_WORD, - 'token': ['text', 'keyword'], - 'unicode': True - }], - "valAdd": [{ - 'regex': START_WORD + get_translated_keyword("to_list") + END_WORD, - 'token': ['text', 'keyword'], - 'next': 'valueTo', - 'unicode': True - }, { - 'regex': "(" + - get_translated_keyword("black", True) + "|" + - get_translated_keyword("blue", True) + "|" + - get_translated_keyword("brown", True) + "|" + - get_translated_keyword("gray", True) + "|" + - get_translated_keyword("green", True) + "|" + - get_translated_keyword("orange", True) + "|" + - get_translated_keyword("pink", True) + "|" + - get_translated_keyword("purple", True) + "|" + - get_translated_keyword("red", True) + "|" + - get_translated_keyword("white", True) + "|" + - get_translated_keyword("yellow", True) + - ")", - 'token': [TOKEN_CONSTANT], - 'unicode': True - }], - "valueTo": [], - "valRemove": [{ - 'regex': START_WORD + get_translated_keyword("from") + END_WORD, - 'token': ['text', 'keyword'], - 'next': 'valueFrom', - 'unicode': True - }, { - 'regex': "(" + - get_translated_keyword("black", True) + "|" + - get_translated_keyword("blue", True) + "|" + - get_translated_keyword("brown", True) + "|" + - get_translated_keyword("gray", True) + "|" + - get_translated_keyword("green", True) + "|" + - get_translated_keyword("orange", True) + "|" + - get_translated_keyword("pink", True) + "|" + - get_translated_keyword("purple", True) + "|" + - get_translated_keyword("red", True) + "|" + - get_translated_keyword("white", True) + "|" + - get_translated_keyword("yellow", True) + - ")", - 'token': [TOKEN_CONSTANT], - 'unicode': True - }], - "valueFrom": [], - }) + return add_extra_rule( + { + "start": [ + { + "regex": START_LINE + + WORD + + SPACE + + get_translated_keyword("is") + + "( *)" + + get_translated_keyword("ask"), + "token": ["text", "text", "text", "keyword", "text", "keyword"], + "next": "valueExpr", + "unicode": True, + }, + { + "regex": START_LINE + WORD + SPACE + get_translated_keyword("is"), + "token": ["text", "text", "text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("print"), + "token": ["text", "keyword"], + "next": "valueExpr", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("turn"), + "token": ["text", "keyword"], + "next": "valueExpr", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("sleep"), + "token": ["text", "keyword"], + "next": "valueExpr", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("forward"), + "token": ["text", "keyword"], + "next": "valueExpr", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("add"), + "token": ["text", "keyword"], + "next": "valAdd", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("remove"), + "token": ["text", "keyword"], + "next": "valRemove", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("color"), + "token": ["text", "keyword"], + "next": "value", + "unicode": True, + }, + ], + "value": [ + { + "regex": START_WORD + + get_translated_keyword("at") + + SPACE + + get_translated_keyword("random"), + "token": ["text", "keyword", "keyword", "keyword"], + "unicode": True, + }, + { + "regex": START_WORD + get_translated_keyword("at") + END_WORD, + "token": ["text", "keyword"], + "unicode": True, + }, + { + "regex": get_translated_keyword("comma"), + "token": ["keyword"], + "unicode": True, + }, + { + "regex": "(" + + get_translated_keyword("black", True) + + "|" + + get_translated_keyword("blue", True) + + "|" + + get_translated_keyword("brown", True) + + "|" + + get_translated_keyword("gray", True) + + "|" + + get_translated_keyword("green", True) + + "|" + + get_translated_keyword("orange", True) + + "|" + + get_translated_keyword("pink", True) + + "|" + + get_translated_keyword("purple", True) + + "|" + + get_translated_keyword("red", True) + + "|" + + get_translated_keyword("white", True) + + "|" + + get_translated_keyword("yellow", True) + + ")", + "token": [TOKEN_CONSTANT], + "unicode": True, + }, + ], + "valueExpr": [ + { + "regex": START_WORD + + get_translated_keyword("at") + + SPACE + + get_translated_keyword("random"), + "token": ["text", "keyword", "keyword", "keyword"], + "unicode": True, + }, + { + "regex": START_WORD + get_translated_keyword("at") + END_WORD, + "token": ["text", "keyword"], + "unicode": True, + }, + ], + "valAdd": [ + { + "regex": START_WORD + get_translated_keyword("to_list") + END_WORD, + "token": ["text", "keyword"], + "next": "valueTo", + "unicode": True, + }, + { + "regex": "(" + + get_translated_keyword("black", True) + + "|" + + get_translated_keyword("blue", True) + + "|" + + get_translated_keyword("brown", True) + + "|" + + get_translated_keyword("gray", True) + + "|" + + get_translated_keyword("green", True) + + "|" + + get_translated_keyword("orange", True) + + "|" + + get_translated_keyword("pink", True) + + "|" + + get_translated_keyword("purple", True) + + "|" + + get_translated_keyword("red", True) + + "|" + + get_translated_keyword("white", True) + + "|" + + get_translated_keyword("yellow", True) + + ")", + "token": [TOKEN_CONSTANT], + "unicode": True, + }, + ], + "valueTo": [], + "valRemove": [ + { + "regex": START_WORD + get_translated_keyword("from") + END_WORD, + "token": ["text", "keyword"], + "next": "valueFrom", + "unicode": True, + }, + { + "regex": "(" + + get_translated_keyword("black", True) + + "|" + + get_translated_keyword("blue", True) + + "|" + + get_translated_keyword("brown", True) + + "|" + + get_translated_keyword("gray", True) + + "|" + + get_translated_keyword("green", True) + + "|" + + get_translated_keyword("orange", True) + + "|" + + get_translated_keyword("pink", True) + + "|" + + get_translated_keyword("purple", True) + + "|" + + get_translated_keyword("red", True) + + "|" + + get_translated_keyword("white", True) + + "|" + + get_translated_keyword("yellow", True) + + ")", + "token": [TOKEN_CONSTANT], + "unicode": True, + }, + ], + "valueFrom": [], + } + ) def rule_level4(): - return add_extra_rule({"start": [{ - 'regex': START_LINE + WORD + SPACE + get_translated_keyword("is") + "( *)" + get_translated_keyword("ask"), - 'token': ["text", 'text', 'text', 'keyword', 'text', 'keyword'], - 'next': 'valueExpr', - 'unicode': True - }, { - 'regex': START_LINE + WORD + SPACE + get_translated_keyword("is"), - 'token': ["text", 'text', 'text', 'keyword'], - 'next': 'value', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("print"), - 'token': ['text', 'keyword'], - 'next': 'valueExpr', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("turn"), - 'token': ['text', 'keyword'], - 'next': 'valueSimple', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("sleep"), - 'token': ['text', 'keyword'], - 'next': 'valueSimple', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("forward"), - 'token': ['text', 'keyword'], - 'next': 'valueSimple', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("color"), - 'token': ["text", 'keyword'], - 'next': 'valueSimple', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("add"), - 'token': ["text", 'keyword'], - 'next': 'valAdd', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("remove"), - 'token': ["text", 'keyword'], - 'next': 'valRemove', - 'unicode': True - }, { - 'regex': START_LINE + get_translated_keyword("clear"), - 'token': ['text', 'event'], - 'unicode': True - }], - "value": [{ - 'regex': START_WORD + get_translated_keyword("at") + SPACE + get_translated_keyword("random"), - 'token': ['text', 'keyword', 'keyword', 'keyword'], - 'unicode': True - }, { - 'regex': START_WORD + get_translated_keyword("at") + END_WORD, - 'token': ['text', 'keyword'], - 'unicode': True - }, { - 'regex': get_translated_keyword("comma"), - 'token': ['keyword'], - 'unicode': True - }, { - 'regex': "(" + - get_translated_keyword("black", True) + "|" + - get_translated_keyword("blue", True) + "|" + - get_translated_keyword("brown", True) + "|" + - get_translated_keyword("gray", True) + "|" + - get_translated_keyword("green", True) + "|" + - get_translated_keyword("orange", True) + "|" + - get_translated_keyword("pink", True) + "|" + - get_translated_keyword("purple", True) + "|" + - get_translated_keyword("red", True) + "|" + - get_translated_keyword("white", True) + "|" + - get_translated_keyword("yellow", True) + - ")", - 'token': [TOKEN_CONSTANT], - 'unicode': True - }], - "valueExpr": [{ - 'regex': START_WORD + get_translated_keyword("at") + SPACE + get_translated_keyword("random"), - 'token': ['text', 'keyword', 'keyword', 'keyword'], - 'unicode': True - }, { - 'regex': START_WORD + get_translated_keyword("at") + END_WORD, - 'token': ['text', 'keyword'], - 'unicode': True - }, { - 'regex': '\"[^\"]*\"', - 'token': 'constant.character', - 'unicode': True - }, { - 'regex': "\'[^\']*\'", - 'token': 'constant.character', - 'unicode': True - }, { - 'regex': "«[^»]*»", - 'token': 'constant.character', - 'unicode': True - }, { - 'regex': '\"[^\"]*$', - 'token': 'text', - 'next': 'start', - 'unicode': True - }, { - 'regex': "\'[^\']*$", - 'token': 'text', - 'next': 'start', - 'unicode': True - }, { - 'regex': "«[^»]*$", - 'token': 'text', - 'next': 'start', - 'unicode': True - }], - "valueSimple": [{ - 'regex': START_WORD + get_translated_keyword("at") + SPACE + get_translated_keyword("random"), - 'token': ['text', 'keyword', 'keyword', 'keyword'], - 'unicode': True - }, { - 'regex': START_WORD + get_translated_keyword("at") + END_WORD, - 'token': ['text', 'keyword'], - 'unicode': True - }, { - 'regex': "(" + - get_translated_keyword("black", True) + "|" + - get_translated_keyword("blue", True) + "|" + - get_translated_keyword("brown", True) + "|" + - get_translated_keyword("gray", True) + "|" + - get_translated_keyword("green", True) + "|" + - get_translated_keyword("orange", True) + "|" + - get_translated_keyword("pink", True) + "|" + - get_translated_keyword("purple", True) + "|" + - get_translated_keyword("red", True) + "|" + - get_translated_keyword("white", True) + "|" + - get_translated_keyword("yellow", True) + - ")", - 'token': [TOKEN_CONSTANT], - 'unicode': True - }], - "valAdd": [{ - 'regex': START_WORD + get_translated_keyword("to_list") + END_WORD, - 'token': ['text', 'keyword'], - 'next': 'valueTo', - 'unicode': True - }, { - 'regex': "(" + - get_translated_keyword("black", True) + "|" + - get_translated_keyword("blue", True) + "|" + - get_translated_keyword("brown", True) + "|" + - get_translated_keyword("gray", True) + "|" + - get_translated_keyword("green", True) + "|" + - get_translated_keyword("orange", True) + "|" + - get_translated_keyword("pink", True) + "|" + - get_translated_keyword("purple", True) + "|" + - get_translated_keyword("red", True) + "|" + - get_translated_keyword("white", True) + "|" + - get_translated_keyword("yellow", True) + - ")", - 'token': [TOKEN_CONSTANT], - 'unicode': True - }], - "valueTo": [], - "valRemove": [{ - 'regex': START_WORD + get_translated_keyword("from") + END_WORD, - 'token': ['text', 'keyword'], - 'next': 'valueFrom', - 'unicode': True - }, { - 'regex': "(" + - get_translated_keyword("black", True) + "|" + - get_translated_keyword("blue", True) + "|" + - get_translated_keyword("brown", True) + "|" + - get_translated_keyword("gray", True) + "|" + - get_translated_keyword("green", True) + "|" + - get_translated_keyword("orange", True) + "|" + - get_translated_keyword("pink", True) + "|" + - get_translated_keyword("purple", True) + "|" + - get_translated_keyword("red", True) + "|" + - get_translated_keyword("white", True) + "|" + - get_translated_keyword("yellow", True) + - ")", - 'token': [TOKEN_CONSTANT], - 'unicode': True - }], - "valueFrom": [], - }) + return add_extra_rule( + { + "start": [ + { + "regex": START_LINE + + WORD + + SPACE + + get_translated_keyword("is") + + "( *)" + + get_translated_keyword("ask"), + "token": ["text", "text", "text", "keyword", "text", "keyword"], + "next": "valueExpr", + "unicode": True, + }, + { + "regex": START_LINE + WORD + SPACE + get_translated_keyword("is"), + "token": ["text", "text", "text", "keyword"], + "next": "value", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("print"), + "token": ["text", "keyword"], + "next": "valueExpr", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("turn"), + "token": ["text", "keyword"], + "next": "valueSimple", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("sleep"), + "token": ["text", "keyword"], + "next": "valueSimple", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("forward"), + "token": ["text", "keyword"], + "next": "valueSimple", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("color"), + "token": ["text", "keyword"], + "next": "valueSimple", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("add"), + "token": ["text", "keyword"], + "next": "valAdd", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("remove"), + "token": ["text", "keyword"], + "next": "valRemove", + "unicode": True, + }, + { + "regex": START_LINE + get_translated_keyword("clear"), + "token": ["text", "event"], + "unicode": True, + }, + ], + "value": [ + { + "regex": START_WORD + + get_translated_keyword("at") + + SPACE + + get_translated_keyword("random"), + "token": ["text", "keyword", "keyword", "keyword"], + "unicode": True, + }, + { + "regex": START_WORD + get_translated_keyword("at") + END_WORD, + "token": ["text", "keyword"], + "unicode": True, + }, + { + "regex": get_translated_keyword("comma"), + "token": ["keyword"], + "unicode": True, + }, + { + "regex": "(" + + get_translated_keyword("black", True) + + "|" + + get_translated_keyword("blue", True) + + "|" + + get_translated_keyword("brown", True) + + "|" + + get_translated_keyword("gray", True) + + "|" + + get_translated_keyword("green", True) + + "|" + + get_translated_keyword("orange", True) + + "|" + + get_translated_keyword("pink", True) + + "|" + + get_translated_keyword("purple", True) + + "|" + + get_translated_keyword("red", True) + + "|" + + get_translated_keyword("white", True) + + "|" + + get_translated_keyword("yellow", True) + + ")", + "token": [TOKEN_CONSTANT], + "unicode": True, + }, + ], + "valueExpr": [ + { + "regex": START_WORD + + get_translated_keyword("at") + + SPACE + + get_translated_keyword("random"), + "token": ["text", "keyword", "keyword", "keyword"], + "unicode": True, + }, + { + "regex": START_WORD + get_translated_keyword("at") + END_WORD, + "token": ["text", "keyword"], + "unicode": True, + }, + {"regex": '"[^"]*"', "token": "constant.character", "unicode": True}, + {"regex": "'[^']*'", "token": "constant.character", "unicode": True}, + {"regex": "«[^»]*»", "token": "constant.character", "unicode": True}, + {"regex": '"[^"]*$', "token": "text", "next": "start", "unicode": True}, + {"regex": "'[^']*$", "token": "text", "next": "start", "unicode": True}, + {"regex": "«[^»]*$", "token": "text", "next": "start", "unicode": True}, + ], + "valueSimple": [ + { + "regex": START_WORD + + get_translated_keyword("at") + + SPACE + + get_translated_keyword("random"), + "token": ["text", "keyword", "keyword", "keyword"], + "unicode": True, + }, + { + "regex": START_WORD + get_translated_keyword("at") + END_WORD, + "token": ["text", "keyword"], + "unicode": True, + }, + { + "regex": "(" + + get_translated_keyword("black", True) + + "|" + + get_translated_keyword("blue", True) + + "|" + + get_translated_keyword("brown", True) + + "|" + + get_translated_keyword("gray", True) + + "|" + + get_translated_keyword("green", True) + + "|" + + get_translated_keyword("orange", True) + + "|" + + get_translated_keyword("pink", True) + + "|" + + get_translated_keyword("purple", True) + + "|" + + get_translated_keyword("red", True) + + "|" + + get_translated_keyword("white", True) + + "|" + + get_translated_keyword("yellow", True) + + ")", + "token": [TOKEN_CONSTANT], + "unicode": True, + }, + ], + "valAdd": [ + { + "regex": START_WORD + get_translated_keyword("to_list") + END_WORD, + "token": ["text", "keyword"], + "next": "valueTo", + "unicode": True, + }, + { + "regex": "(" + + get_translated_keyword("black", True) + + "|" + + get_translated_keyword("blue", True) + + "|" + + get_translated_keyword("brown", True) + + "|" + + get_translated_keyword("gray", True) + + "|" + + get_translated_keyword("green", True) + + "|" + + get_translated_keyword("orange", True) + + "|" + + get_translated_keyword("pink", True) + + "|" + + get_translated_keyword("purple", True) + + "|" + + get_translated_keyword("red", True) + + "|" + + get_translated_keyword("white", True) + + "|" + + get_translated_keyword("yellow", True) + + ")", + "token": [TOKEN_CONSTANT], + "unicode": True, + }, + ], + "valueTo": [], + "valRemove": [ + { + "regex": START_WORD + get_translated_keyword("from") + END_WORD, + "token": ["text", "keyword"], + "next": "valueFrom", + "unicode": True, + }, + { + "regex": "(" + + get_translated_keyword("black", True) + + "|" + + get_translated_keyword("blue", True) + + "|" + + get_translated_keyword("brown", True) + + "|" + + get_translated_keyword("gray", True) + + "|" + + get_translated_keyword("green", True) + + "|" + + get_translated_keyword("orange", True) + + "|" + + get_translated_keyword("pink", True) + + "|" + + get_translated_keyword("purple", True) + + "|" + + get_translated_keyword("red", True) + + "|" + + get_translated_keyword("white", True) + + "|" + + get_translated_keyword("yellow", True) + + ")", + "token": [TOKEN_CONSTANT], + "unicode": True, + }, + ], + "valueFrom": [], + } + ) def add_extra_rule(automaton): for state in automaton: if state != "start": - automaton[state].insert(0, { - 'regex': "(^|$)", - 'token': ["text"], - 'next': 'start', - }) - automaton[state].insert(0, { - 'regex': "#.*$", - 'token': "comment", - 'next': 'start', - }) - automaton[state].insert(0, { - 'regex': "_\\?_", - 'token': "invalid", - 'next': state, - }) - automaton[state].insert(0, { - 'regex': '(^| )(_)(?= |$)', - 'token': ['text', 'invalid'], - 'next': state, - }) + automaton[state].insert( + 0, + { + "regex": "(^|$)", + "token": ["text"], + "next": "start", + }, + ) + automaton[state].insert( + 0, + { + "regex": "#.*$", + "token": "comment", + "next": "start", + }, + ) + automaton[state].insert( + 0, + { + "regex": "_\\?_", + "token": "invalid", + "next": state, + }, + ) + automaton[state].insert( + 0, + { + "regex": "(^| )(_)(?= |$)", + "token": ["text", "invalid"], + "next": state, + }, + ) return automaton diff --git a/highlighting/rules_list.py b/highlighting/rules_list.py index 8614e0dee41..c2bd09fdc53 100644 --- a/highlighting/rules_list.py +++ b/highlighting/rules_list.py @@ -1,5 +1,10 @@ -from definition import (DIGIT, END_WORD, START_WORD, TOKEN_CONSTANT, - get_translated_keyword) +from definition import ( + DIGIT, + END_WORD, + START_WORD, + TOKEN_CONSTANT, + get_translated_keyword, +) from list_keywords import LEVELS, SECONDARY_COLOR_WORDS # After the level 4 @@ -10,7 +15,6 @@ def rule_all(level): - # get keyword by level data_level = LEVELS[level] @@ -18,109 +22,177 @@ def rule_all(level): list_rules = data_level["extra_rules"] # Rule for comments : - list_rules.append({'regex': '#.*$', 'token': 'comment', 'next': 'start', 'unicode': True}) + list_rules.append( + {"regex": "#.*$", "token": "comment", "next": "start", "unicode": True} + ) # Rule for quoted string : # complete - list_rules.append({'regex': '\"[^\"]*\"', 'token': 'constant.character', 'next': 'start', 'unicode': True}) - list_rules.append({'regex': "\'[^\']*\'", 'token': 'constant.character', 'next': 'start', 'unicode': True}) - list_rules.append({'regex': "«[^»]*»", 'token': 'constant.character', 'next': 'start', 'unicode': True}) + list_rules.append( + { + "regex": '"[^"]*"', + "token": "constant.character", + "next": "start", + "unicode": True, + } + ) + list_rules.append( + { + "regex": "'[^']*'", + "token": "constant.character", + "next": "start", + "unicode": True, + } + ) + list_rules.append( + { + "regex": "«[^»]*»", + "token": "constant.character", + "next": "start", + "unicode": True, + } + ) # incomplete - list_rules.append({'regex': '\"[^\"]*$', 'token': 'text', 'next': 'start', 'unicode': True}) - list_rules.append({'regex': "\'[^\']*$", 'token': 'text', 'next': 'start', 'unicode': True}) - list_rules.append({'regex': "«[^»]*$", 'token': 'text', 'next': 'start', 'unicode': True}) + list_rules.append( + {"regex": '"[^"]*$', "token": "text", "next": "start", "unicode": True} + ) + list_rules.append( + {"regex": "'[^']*$", "token": "text", "next": "start", "unicode": True} + ) + list_rules.append( + {"regex": "«[^»]*$", "token": "text", "next": "start", "unicode": True} + ) # Rule for blanks marks : - list_rules.append({'regex': '_\\?_', 'token': 'invalid', 'next': 'start', 'unicode': True}) - list_rules.append({'regex': '(^| )(_)(?= |$)', 'token': ['text', 'invalid'], 'next': 'start', 'unicode': True}) + list_rules.append( + {"regex": "_\\?_", "token": "invalid", "next": "start", "unicode": True} + ) + list_rules.append( + { + "regex": "(^| )(_)(?= |$)", + "token": ["text", "invalid"], + "next": "start", + "unicode": True, + } + ) # Rules for numbers - if (data_level["number"]): - if (data_level["number_with_decimal"]): - number_regex = '(' + DIGIT + '*\\.?' + DIGIT + '+)' + if data_level["number"]: + if data_level["number_with_decimal"]: + number_regex = "(" + DIGIT + "*\\.?" + DIGIT + "+)" else: - number_regex = '(' + DIGIT + '+)' + number_regex = "(" + DIGIT + "+)" - list_rules.append({'regex': START_WORD + number_regex + END_WORD, - 'token': ['text', 'variable'], 'next': 'start', 'unicode': True}) + list_rules.append( + { + "regex": START_WORD + number_regex + END_WORD, + "token": ["text", "variable"], + "next": "start", + "unicode": True, + } + ) # Special case of an number directly followed by a number for command in data_level["space_before"]: - list_rules.append({ - 'regex': START_WORD + get_translated_keyword(command) + number_regex + END_WORD, - 'token': ['text', 'keyword', 'variable'], - 'next': 'start', - 'unicode': True - }) + list_rules.append( + { + "regex": START_WORD + + get_translated_keyword(command) + + number_regex + + END_WORD, + "token": ["text", "keyword", "variable"], + "next": "start", + "unicode": True, + } + ) for command in data_level["no_space"]: - list_rules.append({ - 'regex': get_translated_keyword(command) + number_regex + END_WORD, - 'token': ['keyword', 'variable'], - 'next': 'start', - 'unicode': True - }) + list_rules.append( + { + "regex": get_translated_keyword(command) + number_regex + END_WORD, + "token": ["keyword", "variable"], + "next": "start", + "unicode": True, + } + ) # Rules for commands of space_before_and_after # These are the keywords that must be "alone" so neither preceded nor followed directly by a word for command in data_level["space_before_and_after"]: - list_rules.append({ - 'regex': START_WORD + get_translated_keyword(command) + END_WORD, - 'token': ["text", "support.function" if command in SECONDARY_COLOR_WORDS else "keyword"], - 'next': "start", - 'unicode': True - }) + list_rules.append( + { + "regex": START_WORD + get_translated_keyword(command) + END_WORD, + "token": [ + "text", + "support.function" + if command in SECONDARY_COLOR_WORDS + else "keyword", + ], + "next": "start", + "unicode": True, + } + ) # Rules for commands of no_space # These are the keywords that are independent of the context (formerly the symbols # In particular, even if they are between 2 words, the syntax highlighting will select them for command in data_level["no_space"]: - list_rules.append({ - 'regex': get_translated_keyword(command), - 'token': ["keyword"], - 'next': "start", - 'unicode': True - }) + list_rules.append( + { + "regex": get_translated_keyword(command), + "token": ["keyword"], + "next": "start", + "unicode": True, + } + ) # Rules for commands of space_before # This category of keywords allows you to have keywords that are not preced # by another word, but that can be followed immediately by another word. (see the PR #2413)*/ for command in data_level["space_before"]: - list_rules.append({ - 'regex': START_WORD + get_translated_keyword(command), - 'token': ["text", "keyword"], - 'next': "start", - 'unicode': True - }) + list_rules.append( + { + "regex": START_WORD + get_translated_keyword(command), + "token": ["text", "keyword"], + "next": "start", + "unicode": True, + } + ) # Rules for commands of space_after # This category of keywords allows you to have keywords that can be preceded immediate # by another word, but that are not followed by another word.*/ for command in data_level["space_after"]: - list_rules.append({ - 'regex': get_translated_keyword(command) + END_WORD, - 'token': ["keyword"], - 'next': "start", - 'unicode': True - }) + list_rules.append( + { + "regex": get_translated_keyword(command) + END_WORD, + "token": ["keyword"], + "next": "start", + "unicode": True, + } + ) # Rules for constants (colors, directions) - for command in data_level['constant']: - list_rules.append({ - 'regex': START_WORD + get_translated_keyword(command) + END_WORD, - 'token': ["text", TOKEN_CONSTANT], - 'next': "start", - 'unicode': True - }) + for command in data_level["constant"]: + list_rules.append( + { + "regex": START_WORD + get_translated_keyword(command) + END_WORD, + "token": ["text", TOKEN_CONSTANT], + "next": "start", + "unicode": True, + } + ) # Rules for events ('pressed') for command in data_level["event"]: - list_rules.append({ - 'regex': START_WORD + get_translated_keyword(command) + END_WORD, - 'token': ["text", "event"], - 'next': "start", - 'unicode': True - }) + list_rules.append( + { + "regex": START_WORD + get_translated_keyword(command) + END_WORD, + "token": ["text", "event"], + "next": "start", + "unicode": True, + } + ) return {"start": list_rules} diff --git a/logging_config.py b/logging_config.py index 29a48bf2790..f9972bd09fb 100644 --- a/logging_config.py +++ b/logging_config.py @@ -2,26 +2,26 @@ LOGGING_CONFIG = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'standard': { - 'format': '[%(asctime)s] %(filename)s %(funcName)s %(lineno)d <%(levelname)s>: ' - '%(message)s' + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "standard": { + "format": "[%(asctime)s] %(filename)s %(funcName)s %(lineno)d <%(levelname)s>: " + "%(message)s" }, }, - 'handlers': { - 'default': { - 'level': 'DEBUG' if not os.getenv('NO_DEBUG_MODE') else 'INFO', - 'formatter': 'standard', - 'class': 'logging.StreamHandler', + "handlers": { + "default": { + "level": "DEBUG" if not os.getenv("NO_DEBUG_MODE") else "INFO", + "formatter": "standard", + "class": "logging.StreamHandler", }, }, - 'loggers': { - '': { - 'handlers': ['default'], - 'level': 'DEBUG' if not os.getenv('NO_DEBUG_MODE') else 'INFO', - 'propagate': False + "loggers": { + "": { + "handlers": ["default"], + "level": "DEBUG" if not os.getenv("NO_DEBUG_MODE") else "INFO", + "propagate": False, }, - } + }, } diff --git a/prefixes/normal.py b/prefixes/normal.py index 6495b1e3173..4546fa642c8 100644 --- a/prefixes/normal.py +++ b/prefixes/normal.py @@ -18,80 +18,271 @@ def int(s): if isinstance(s, str): - numerals_dict = {'0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', - '9': '9', '𑁦': '0', '𑁧': '1', '𑁨': '2', '𑁩': '3', '𑁪': '4', '𑁫': '5', '𑁬': '6', '𑁭': '7', - '𑁮': '8', '𑁯': '9', '०': '0', '१': '1', '२': '2', '३': '3', '४': '4', '५': '5', '६': '6', - '७': '7', '८': '8', '९': '9', '૦': '0', '૧': '1', '૨': '2', '૩': '3', '૪': '4', '૫': '5', - '૬': '6', '૭': '7', '૮': '8', '૯': '9', '੦': '0', '੧': '1', '੨': '2', '੩': '3', '੪': '4', - '੫': '5', '੬': '6', '੭': '7', '੮': '8', '੯': '9', '০': '0', '১': '1', '২': '2', '৩': '3', - '৪': '4', '৫': '5', '৬': '6', '৭': '7', '৮': '8', '৯': '9', '೦': '0', '೧': '1', '೨': '2', - '೩': '3', '೪': '4', '೫': '5', '೬': '6', '೭': '7', '೮': '8', '೯': '9', '୦': '0', '୧': '1', - '୨': '2', '୩': '3', '୪': '4', '୫': '5', '୬': '6', '୭': '7', '୮': '8', '୯': '9', '൦': '0', - '൧': '1', '൨': '2', '൩': '3', '൪': '4', '൫': '5', '൬': '6', '൭': '7', '൮': '8', '൯': '9', - '௦': '0', '௧': '1', '௨': '2', '௩': '3', '௪': '4', '௫': '5', '௬': '6', '௭': '7', '௮': '8', - '௯': '9', '౦': '0', '౧': '1', '౨': '2', '౩': '3', '౪': '4', '౫': '5', '౬': '6', '౭': '7', - '౮': '8', '౯': '9', '၀': '0', '၁': '1', '၂': '2', '၃': '3', '၄': '4', '၅': '5', '၆': '6', - '၇': '7', '၈': '8', '၉': '9', '༠': '0', '༡': '1', '༢': '2', '༣': '3', '༤': '4', '༥': '5', - '༦': '6', '༧': '7', '༨': '8', '༩': '9', '᠐': '0', '᠑': '1', '᠒': '2', '᠓': '3', '᠔': '4', - '᠕': '5', '᠖': '6', '᠗': '7', '᠘': '8', '᠙': '9', '០': '0', '១': '1', '២': '2', '៣': '3', - '៤': '4', '៥': '5', '៦': '6', '៧': '7', '៨': '8', '៩': '9', '๐': '0', '๑': '1', '๒': '2', - '๓': '3', '๔': '4', '๕': '5', '๖': '6', '๗': '7', '๘': '8', '๙': '9', '໐': '0', '໑': '1', - '໒': '2', '໓': '3', '໔': '4', '໕': '5', '໖': '6', '໗': '7', '໘': '8', '໙': '9', '꧐': '0', - '꧑': '1', '꧒': '2', '꧓': '3', '꧔': '4', '꧕': '5', '꧖': '6', '꧗': '7', '꧘': '8', '꧙': '9', - '٠': '0', '١': '1', '٢': '2', '٣': '3', '٤': '4', '٥': '5', '٦': '6', '٧': '7', '٨': '8', - '٩': '9', '۰': '0', '۱': '1', '۲': '2', '۳': '3', '۴': '4', '۵': '5', '۶': '6', '۷': '7', - '۸': '8', '۹': '9', '〇': '0', '一': '1', '二': '2', '三': '3', '四': '4', '五': '5', '六': '6', - '七': '7', '八': '8', '九': '9', '零': '0'} - latin_numerals = ''.join([numerals_dict.get(letter, letter) for letter in s]) + numerals_dict = { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9", + "𑁦": "0", + "𑁧": "1", + "𑁨": "2", + "𑁩": "3", + "𑁪": "4", + "𑁫": "5", + "𑁬": "6", + "𑁭": "7", + "𑁮": "8", + "𑁯": "9", + "०": "0", + "१": "1", + "२": "2", + "३": "3", + "४": "4", + "५": "5", + "६": "6", + "७": "7", + "८": "8", + "९": "9", + "૦": "0", + "૧": "1", + "૨": "2", + "૩": "3", + "૪": "4", + "૫": "5", + "૬": "6", + "૭": "7", + "૮": "8", + "૯": "9", + "੦": "0", + "੧": "1", + "੨": "2", + "੩": "3", + "੪": "4", + "੫": "5", + "੬": "6", + "੭": "7", + "੮": "8", + "੯": "9", + "০": "0", + "১": "1", + "২": "2", + "৩": "3", + "৪": "4", + "৫": "5", + "৬": "6", + "৭": "7", + "৮": "8", + "৯": "9", + "೦": "0", + "೧": "1", + "೨": "2", + "೩": "3", + "೪": "4", + "೫": "5", + "೬": "6", + "೭": "7", + "೮": "8", + "೯": "9", + "୦": "0", + "୧": "1", + "୨": "2", + "୩": "3", + "୪": "4", + "୫": "5", + "୬": "6", + "୭": "7", + "୮": "8", + "୯": "9", + "൦": "0", + "൧": "1", + "൨": "2", + "൩": "3", + "൪": "4", + "൫": "5", + "൬": "6", + "൭": "7", + "൮": "8", + "൯": "9", + "௦": "0", + "௧": "1", + "௨": "2", + "௩": "3", + "௪": "4", + "௫": "5", + "௬": "6", + "௭": "7", + "௮": "8", + "௯": "9", + "౦": "0", + "౧": "1", + "౨": "2", + "౩": "3", + "౪": "4", + "౫": "5", + "౬": "6", + "౭": "7", + "౮": "8", + "౯": "9", + "၀": "0", + "၁": "1", + "၂": "2", + "၃": "3", + "၄": "4", + "၅": "5", + "၆": "6", + "၇": "7", + "၈": "8", + "၉": "9", + "༠": "0", + "༡": "1", + "༢": "2", + "༣": "3", + "༤": "4", + "༥": "5", + "༦": "6", + "༧": "7", + "༨": "8", + "༩": "9", + "᠐": "0", + "᠑": "1", + "᠒": "2", + "᠓": "3", + "᠔": "4", + "᠕": "5", + "᠖": "6", + "᠗": "7", + "᠘": "8", + "᠙": "9", + "០": "0", + "១": "1", + "២": "2", + "៣": "3", + "៤": "4", + "៥": "5", + "៦": "6", + "៧": "7", + "៨": "8", + "៩": "9", + "๐": "0", + "๑": "1", + "๒": "2", + "๓": "3", + "๔": "4", + "๕": "5", + "๖": "6", + "๗": "7", + "๘": "8", + "๙": "9", + "໐": "0", + "໑": "1", + "໒": "2", + "໓": "3", + "໔": "4", + "໕": "5", + "໖": "6", + "໗": "7", + "໘": "8", + "໙": "9", + "꧐": "0", + "꧑": "1", + "꧒": "2", + "꧓": "3", + "꧔": "4", + "꧕": "5", + "꧖": "6", + "꧗": "7", + "꧘": "8", + "꧙": "9", + "٠": "0", + "١": "1", + "٢": "2", + "٣": "3", + "٤": "4", + "٥": "5", + "٦": "6", + "٧": "7", + "٨": "8", + "٩": "9", + "۰": "0", + "۱": "1", + "۲": "2", + "۳": "3", + "۴": "4", + "۵": "5", + "۶": "6", + "۷": "7", + "۸": "8", + "۹": "9", + "〇": "0", + "一": "1", + "二": "2", + "三": "3", + "四": "4", + "五": "5", + "六": "6", + "七": "7", + "八": "8", + "九": "9", + "零": "0", + } + latin_numerals = "".join([numerals_dict.get(letter, letter) for letter in s]) return int_saver(latin_numerals) - return (int_saver(s)) + return int_saver(s) def convert_numerals(alphabet, number): numerals_dict_return = { - 'Latin': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], - 'Brahmi': ['𑁦', '𑁧', '𑁨', '𑁩', '𑁪', '𑁫', '𑁬', '𑁭', '𑁮', '𑁯'], - 'Devanagari': ['०', '१', '२', '३', '४', '५', '६', '७', '८', '९'], - 'Gujarati': ['૦', '૧', '૨', '૩', '૪', '૫', '૬', '૭', '૮', '૯'], - 'Gurmukhi': ['੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯'], - 'Bengali': ['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], - 'Kannada': ['೦', '೧', '೨', '೩', '೪', '೫', '೬', '೭', '೮', '೯'], - 'Odia': ['୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯'], - 'Malayalam': ['൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯'], - 'Tamil': ['௦', '௧', '௨', '௩', '௪', '௫', '௬', '௭', '௮', '௯'], - 'Telugu': ['౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯'], - 'Burmese': ['၀', '၁', '၂', '၃', '၄', '၅', '၆', '၇', '၈', '၉'], - 'Tibetan': ['༠', '༡', '༢', '༣', '༤', '༥', '༦', '༧', '༨', '༩'], - 'Mongolian': ['᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙'], - 'Khmer': ['០', '១', '២', '៣', '៤', '៥', '៦', '៧', '៨', '៩'], - 'Thai': ['๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙'], - 'Lao': ['໐', '໑', '໒', '໓', '໔', '໕', '໖', '໗', '໘', '໙'], - 'Javanese': ['꧐', '꧑', '꧒', '꧓', '꧔', '꧕', '꧖', '꧗', '꧘', '꧙'], - 'Arabic': ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'], - 'Persian': ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'], - 'Urdu': ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'] + "Latin": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + "Brahmi": ["𑁦", "𑁧", "𑁨", "𑁩", "𑁪", "𑁫", "𑁬", "𑁭", "𑁮", "𑁯"], + "Devanagari": ["०", "१", "२", "३", "४", "५", "६", "७", "८", "९"], + "Gujarati": ["૦", "૧", "૨", "૩", "૪", "૫", "૬", "૭", "૮", "૯"], + "Gurmukhi": ["੦", "੧", "੨", "੩", "੪", "੫", "੬", "੭", "੮", "੯"], + "Bengali": ["০", "১", "২", "৩", "৪", "৫", "৬", "৭", "৮", "৯"], + "Kannada": ["೦", "೧", "೨", "೩", "೪", "೫", "೬", "೭", "೮", "೯"], + "Odia": ["୦", "୧", "୨", "୩", "୪", "୫", "୬", "୭", "୮", "୯"], + "Malayalam": ["൦", "൧", "൨", "൩", "൪", "൫", "൬", "൭", "൮", "൯"], + "Tamil": ["௦", "௧", "௨", "௩", "௪", "௫", "௬", "௭", "௮", "௯"], + "Telugu": ["౦", "౧", "౨", "౩", "౪", "౫", "౬", "౭", "౮", "౯"], + "Burmese": ["၀", "၁", "၂", "၃", "၄", "၅", "၆", "၇", "၈", "၉"], + "Tibetan": ["༠", "༡", "༢", "༣", "༤", "༥", "༦", "༧", "༨", "༩"], + "Mongolian": ["᠐", "᠑", "᠒", "᠓", "᠔", "᠕", "᠖", "᠗", "᠘", "᠙"], + "Khmer": ["០", "១", "២", "៣", "៤", "៥", "៦", "៧", "៨", "៩"], + "Thai": ["๐", "๑", "๒", "๓", "๔", "๕", "๖", "๗", "๘", "๙"], + "Lao": ["໐", "໑", "໒", "໓", "໔", "໕", "໖", "໗", "໘", "໙"], + "Javanese": ["꧐", "꧑", "꧒", "꧓", "꧔", "꧕", "꧖", "꧗", "꧘", "꧙"], + "Arabic": ["٠", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"], + "Persian": ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"], + "Urdu": ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"], } number = str(number) T = str - sign = '' - if number[0] == '-': - sign = '-' + sign = "" + if number[0] == "-": + sign = "-" number = number[1:] - if number.replace('.', '', 1).isnumeric(): + if number.replace(".", "", 1).isnumeric(): numerals_list = numerals_dict_return[alphabet] - if '.' in number: - tokens = number.split('.') + if "." in number: + tokens = number.split(".") all_numerals_converted = [numerals_list[int(digit)] for digit in tokens[0]] - all_numerals_converted.append('.') - all_numerals_converted.extend(numerals_list[int(digit)] for digit in tokens[1]) - if alphabet == 'Latin': + all_numerals_converted.append(".") + all_numerals_converted.extend( + numerals_list[int(digit)] for digit in tokens[1] + ) + if alphabet == "Latin": T = float else: all_numerals_converted = [numerals_list[int(digit)] for digit in number] - if alphabet == 'Latin': + if alphabet == "Latin": T = int - number = ''.join(all_numerals_converted) - return T(f'{sign}{number}') + number = "".join(all_numerals_converted) + return T(f"{sign}{number}") diff --git a/program_repair.py b/program_repair.py index 2cf9cfad3ee..71775fde32d 100644 --- a/program_repair.py +++ b/program_repair.py @@ -1,21 +1,21 @@ def insert(input_string, line, column, new_string): - """"insert new_string at (line, column)""" + """ "insert new_string at (line, column)""" rows = input_string.splitlines() rows[line] = rows[line][:column] + new_string + rows[line][column:] - return '\n'.join(rows) + return "\n".join(rows) def delete(input_string, line, column, length): - """"delete length chars starting at (line, column)""" + """ "delete length chars starting at (line, column)""" rows = input_string.splitlines() - rows[line] = rows[line][:column] + rows[line][column + length:] + rows[line] = rows[line][:column] + rows[line][column + length :] - return '\n'.join(rows) + return "\n".join(rows) def replace(input_string, line, column, length, new_string): - """"replace at (line, column) length chars with new_string""" + """ "replace at (line, column) length chars with new_string""" result = delete(input_string, line, column, length) result = insert(result, line, column, new_string) @@ -24,7 +24,7 @@ def replace(input_string, line, column, length, new_string): def remove_leading_spaces(input_string): # the only repair we can do now is remove leading spaces, more can be added! - return '\n'.join([x.lstrip() for x in input_string.split('\n')]) + return "\n".join([x.lstrip() for x in input_string.split("\n")]) def remove_unexpected_char(input_string, line, column): @@ -34,7 +34,7 @@ def remove_unexpected_char(input_string, line, column): def fix_indent(input_string, line, leading_spaces, indent_size): if leading_spaces < indent_size: # not enough spaces, add spaces - return insert(input_string, line - 1, 0, ' ' * (indent_size - leading_spaces)) + return insert(input_string, line - 1, 0, " " * (indent_size - leading_spaces)) else: # too many spaces, remove spaces return delete(input_string, line - 1, 0, leading_spaces - indent_size) diff --git a/requirements.txt b/requirements.txt index 8755eb9e6a8..c297efcb5fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,3 +30,4 @@ babel==2.11.0 jinja-partials==0.1.1 hypothesis>=6.75.3 tqdm==4.65.0 +black==23.7.0 diff --git a/safe_format.py b/safe_format.py index 6c25e8028a2..b73040310fe 100644 --- a/safe_format.py +++ b/safe_format.py @@ -9,7 +9,7 @@ def safe_format(s: str, /, **kwargs): class SafeFormatter(Formatter): def get_value(self, field_name, args, kwargs): if field_name not in kwargs: - return '{' + field_name + '}' + return "{" + field_name + "}" return super().get_value(field_name, args, kwargs) diff --git a/setup.cfg b/setup.cfg index 3c7241cf15c..b9172c5ebb5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,22 +1,16 @@ -[autopep8] -exclude = - .git, - .env, - venv, - __pycache__, - ./tests/* -max-line-length = 120 - [flake8] -exclude = +extend-ignore = + # E203 is not advised by black, see https://github.com/psf/black/issues/315 + E203 +exclude = .git, .env, venv, __pycache__, -per-file-ignores = +per-file-ignores = ./tests/*:F541 ./utils.py:E501 - ./tests/Tester.py:E501 + ./tests/Tester.py:E501 ./tests/test_level/*:E501,F541, W291 ./tests/test_highlighting/*:E501 ./highlighting/list_keywords.py:E501 diff --git a/static_babel_content.py b/static_babel_content.py index 625f4770eea..52ed0000a97 100644 --- a/static_babel_content.py +++ b/static_babel_content.py @@ -8,9 +8,351 @@ # ################################################### -COUNTRIES = {'AF': 'افغانستان', 'AX': 'Åland', 'AL': 'Shqipëri', 'DZ': 'الجزائر', 'AS': 'American Samoa', 'AD': 'Andorra', 'AO': 'Angola', 'AI': 'Anguilla', 'AQ': 'Antarctica', 'AG': 'Antigua & Barbuda', 'AR': 'Argentina', 'AM': 'Հայաստան', 'AW': 'Aruba', 'AU': 'Australia', 'AT': 'Österreich', 'AZ': 'Azərbaycan', 'BS': 'Bahamas', 'BH': 'البحرين', 'BD': 'বাংলাদেশ', 'BB': 'Barbados', 'BY': 'Беларусь', 'BE': 'Belgium', 'BZ': 'Belize', 'BJ': 'Bénin', 'BM': 'Bermuda', 'BT': 'འབྲུག', 'BO': 'Bolivia', 'BQ': 'Caribisch Nederland', 'BA': 'Bosna i Hercegovina', 'BW': 'Botswana', 'BV': 'Bouvet Island', 'BR': 'Brasil', 'IO': 'British Indian Ocean Territory', 'BN': 'Brunei', 'BG': 'България', 'BF': 'Burkina Faso', 'BI': 'Uburundi', 'KH': 'កម្ពុជា', 'CM': 'Cameroun', 'CA': 'Canada', 'CV': 'Kabu Verdi', 'KY': 'Cayman Islands', 'CF': 'République centrafricaine', 'TD': 'Tchad', 'CL': 'Chile', 'CN': '中国', 'CX': 'Christmas Island', 'CC': 'Cocos (Keeling) Islands', 'CO': 'Colombia', 'KM': 'جزر القمر', 'CG': 'Congo-Brazzaville', 'CD': 'Jamhuri ya Kidemokrasia ya Kongo', 'CK': 'Cook Islands', 'CR': 'Costa Rica', 'CI': 'Côte d’Ivoire', 'HR': 'Hrvatska', 'CU': 'Cuba', 'CW': 'Curaçao', 'CY': 'Κύπρος', 'CZ': 'Česko', 'DK': 'Danmark', 'DJ': 'Jabuuti', 'DM': 'Dominica', 'DO': 'República Dominicana', 'EC': 'Ecuador', 'EG': 'مصر', 'SV': 'El Salvador', 'GQ': 'Guinea Ecuatorial', 'ER': 'ኤርትራ', 'EE': 'Eesti', 'ET': 'ኢትዮጵያ', 'FK': 'Falkland Islands', 'FO': 'Føroyar', 'FJ': 'Fiji', 'FI': 'Suomi', 'FR': 'France', 'GF': 'Guyane française', 'PF': 'Polynésie française', 'TF': 'French Southern Territories', 'GA': 'Gabon', 'GM': 'Gambia', 'GE': 'საქართველო', 'DE': 'Deutschland', 'GH': 'Gaana', 'GI': 'Gibraltar', 'GR': 'Ελλάδα', 'GL': 'Kalaallit Nunaat', 'GD': 'Grenada', 'GP': 'Guadeloupe', 'GU': 'Guam', 'GT': 'Guatemala', 'GG': 'Guernsey', 'GN': 'Guinée', 'GW': 'Guiné-Bissau', 'GY': 'Guyana', 'HT': 'Haïti', 'HM': 'Heard Island and McDonald Islands', 'VA': 'Città del Vaticano', 'HN': 'Honduras', 'HK': '中國香港', 'HU': 'Magyarország', 'IS': 'Ísland', 'IN': 'भारत', 'ID': 'Indonesia', 'IR': 'ایران', 'IQ': 'العراق', 'IE': 'Ireland', 'IM': 'Isle of Man', 'IL': 'ישראל', 'IT': 'Italia', 'JM': 'Jamaica', 'JP': '日本', 'JE': 'Jersey', 'JO': 'الأردن', 'KZ': 'Казахстан', 'KE': 'Kenya', 'KI': 'Kiribati', 'KP': '조선민주주의인민공화국', 'KR': '대한민국', 'XK': 'Kosovë', 'KW': 'الكويت', 'KG': 'Кыргызстан', 'LA': 'ລາວ', 'LV': 'Latvija', 'LB': 'لبنان', 'LS': 'Lesotho', 'LR': 'Liberia', 'LY': 'ليبيا', 'LI': 'Liechtenstein', - 'LT': 'Lietuva', 'LU': 'Luxembourg', 'MO': '中國澳門', 'MK': 'Северна Македонија', 'MG': 'Madagasikara', 'MW': 'Malawi', 'MY': 'Malaysia', 'MV': 'Maldives', 'ML': 'Mali', 'MT': 'Malta', 'MH': 'Marshall Islands', 'MQ': 'Martinique', 'MR': 'موريتانيا', 'MU': 'Moris', 'YT': 'Mayotte', 'MX': 'México', 'FM': 'Micronesia', 'MD': 'Republica Moldova', 'MC': 'Monaco', 'MN': 'Монгол', 'ME': 'Crna Gora', 'MS': 'Montserrat', 'MA': 'المغرب', 'MZ': 'Moçambique', 'MM': 'မြန်မာ', 'NA': 'Namibië', 'NR': 'Nauru', 'NP': 'नेपाल', 'NL': 'Nederland', 'NC': 'Nouvelle-Calédonie', 'NZ': 'New Zealand', 'NI': 'Nicaragua', 'NE': 'Nijar', 'NG': 'Nigeria', 'NU': 'Niue', 'NF': 'Norfolk Island', 'MP': 'Northern Mariana Islands', 'NO': 'Norge', 'OM': 'عُمان', 'PK': 'پاکستان', 'PW': 'Palau', 'PS': 'الأراضي الفلسطينية', 'PA': 'Panamá', 'PG': 'Papua New Guinea', 'PY': 'Paraguay', 'PE': 'Perú', 'PH': 'Philippines', 'PN': 'Pitcairn Islands', 'PL': 'Polska', 'PT': 'Portugal', 'PR': 'Puerto Rico', 'QA': 'قطر', 'RE': 'La Réunion', 'RO': 'România', 'RU': 'Россия', 'RW': 'U Rwanda', 'BL': 'Saint-Barthélemy', 'SH': 'St. Helena', 'KN': 'St. Kitts & Nevis', 'LC': 'St. Lucia', 'MF': 'Saint-Martin', 'PM': 'Saint-Pierre-et-Miquelon', 'VC': 'St. Vincent & Grenadines', 'WS': 'Samoa', 'SM': 'San Marino', 'ST': 'São Tomé e Príncipe', 'SA': 'المملكة العربية السعودية', 'SN': 'Senegaal', 'RS': 'Србија', 'SC': 'Seychelles', 'SL': 'Sierra Leone', 'SG': 'Singapore', 'SX': 'Sint Maarten', 'SK': 'Slovensko', 'SI': 'Slovenija', 'SB': 'Solomon Islands', 'SO': 'Soomaaliya', 'ZA': 'South Africa', 'GS': 'South Georgia and the South Sandwich Islands', 'SS': 'جنوب السودان', 'ES': 'España', 'LK': 'ශ්\u200dරී ලංකාව', 'SD': 'السودان', 'SR': 'Suriname', 'SJ': 'Svalbard og Jan Mayen', 'SZ': 'Eswatini', 'SE': 'Sverige', 'CH': 'Schweiz', 'SY': 'سوريا', 'TW': '台灣', 'TJ': 'Тоҷикистон', 'TZ': 'Tanzania', 'TH': 'ไทย', 'TL': 'Timor-Leste', 'TG': 'Togo', 'TK': 'Tokelau', 'TO': 'Tonga', 'TT': 'Trinidad & Tobago', 'TN': 'تونس', 'TR': 'Türkiye', 'TM': 'Türkmenistan', 'TC': 'Turks & Caicos Islands', 'TV': 'Tuvalu', 'UG': 'Uganda', 'UA': 'Україна', 'AE': 'الإمارات العربية المتحدة', 'GB': 'United Kingdom', 'US': 'United States', 'UM': 'U.S. Outlying Islands', 'UY': 'Uruguay', 'UZ': 'Oʻzbekiston', 'VU': 'Vanuatu', 'VE': 'Venezuela', 'VN': 'Việt Nam', 'VG': 'British Virgin Islands', 'VI': 'U.S. Virgin Islands', 'WF': 'Wallis-et-Futuna', 'EH': 'الصحراء الغربية', 'YE': 'اليمن', 'ZM': 'Zambia', 'ZW': 'Zimbabwe'} -LANGUAGE_NAMES = {'ur': 'اردو', 'kmr': 'Kurdî (Tirkiye)', 'sw': 'Kiswahili', 'pl': 'Polski', 'vi': 'Tiếng Việt', 'sq': 'Shqip', 'sv': 'Svenska', 'he': 'עברית', 'da': 'Dansk', 'pt_BR': 'Português (Brasil)', 'ja': '日本語', 'el': 'Ελληνικά', 'fy': 'Frysk', 'it': 'Italiano', 'ca': 'Català', 'nb_NO': 'Norsk Bokmål (Norge)', 'cs': 'Čeština', 'pa_PK': 'پنجابی (عربی, پاکستان)', 'te': 'తెలుగు', 'pt_PT': 'Português (Portugal)', 'zh_Hans': '中文 (简体)', - 'ru': 'Русский', 'tl': 'Filipino (Pilipinas)', 'zh_Hant': '中文 (繁體)', 'ro': 'Română', 'uk': 'Українська', 'sr': 'Српски', 'ar': 'العربية', 'hu': 'Magyar', 'nl': 'Nederlands', 'bg': 'Български', 'bn': 'বাংলা', 'hi': 'हिन्दी', 'de': 'Deutsch', 'ko': '한국어', 'fi': 'Suomi', 'eo': 'Esperanto', 'id': 'Indonesia', 'fr': 'Français', 'es': 'Español', 'et': 'Eesti', 'en': 'English', 'fa': 'فارسی', 'cy': 'Cymraeg', 'th': 'ไทย', 'tr': 'Türkçe'} -TEXT_DIRECTIONS = {'ur': 'rtl', 'kmr': 'ltr', 'sw': 'ltr', 'pl': 'ltr', 'vi': 'ltr', 'sq': 'ltr', 'sv': 'ltr', 'he': 'rtl', 'da': 'ltr', 'pt_BR': 'ltr', 'ja': 'ltr', 'el': 'ltr', 'fy': 'ltr', 'it': 'ltr', 'ca': 'ltr', 'nb_NO': 'ltr', 'cs': 'ltr', 'pa_PK': 'rtl', 'te': 'ltr', 'pt_PT': 'ltr', 'zh_Hans': 'ltr', 'ru': 'ltr', - 'tl': 'ltr', 'zh_Hant': 'ltr', 'ro': 'ltr', 'uk': 'ltr', 'sr': 'ltr', 'ar': 'rtl', 'hu': 'ltr', 'nl': 'ltr', 'bg': 'ltr', 'bn': 'ltr', 'hi': 'ltr', 'de': 'ltr', 'ko': 'ltr', 'fi': 'ltr', 'eo': 'ltr', 'id': 'ltr', 'fr': 'ltr', 'es': 'ltr', 'et': 'ltr', 'en': 'ltr', 'fa': 'rtl', 'cy': 'ltr', 'th': 'ltr', 'tr': 'ltr'} +COUNTRIES = { + "AF": "افغانستان", + "AX": "Åland", + "AL": "Shqipëri", + "DZ": "الجزائر", + "AS": "American Samoa", + "AD": "Andorra", + "AO": "Angola", + "AI": "Anguilla", + "AQ": "Antarctica", + "AG": "Antigua & Barbuda", + "AR": "Argentina", + "AM": "Հայաստան", + "AW": "Aruba", + "AU": "Australia", + "AT": "Österreich", + "AZ": "Azərbaycan", + "BS": "Bahamas", + "BH": "البحرين", + "BD": "বাংলাদেশ", + "BB": "Barbados", + "BY": "Беларусь", + "BE": "Belgium", + "BZ": "Belize", + "BJ": "Bénin", + "BM": "Bermuda", + "BT": "འབྲུག", + "BO": "Bolivia", + "BQ": "Caribisch Nederland", + "BA": "Bosna i Hercegovina", + "BW": "Botswana", + "BV": "Bouvet Island", + "BR": "Brasil", + "IO": "British Indian Ocean Territory", + "BN": "Brunei", + "BG": "България", + "BF": "Burkina Faso", + "BI": "Uburundi", + "KH": "កម្ពុជា", + "CM": "Cameroun", + "CA": "Canada", + "CV": "Kabu Verdi", + "KY": "Cayman Islands", + "CF": "République centrafricaine", + "TD": "Tchad", + "CL": "Chile", + "CN": "中国", + "CX": "Christmas Island", + "CC": "Cocos (Keeling) Islands", + "CO": "Colombia", + "KM": "جزر القمر", + "CG": "Congo-Brazzaville", + "CD": "Jamhuri ya Kidemokrasia ya Kongo", + "CK": "Cook Islands", + "CR": "Costa Rica", + "CI": "Côte d’Ivoire", + "HR": "Hrvatska", + "CU": "Cuba", + "CW": "Curaçao", + "CY": "Κύπρος", + "CZ": "Česko", + "DK": "Danmark", + "DJ": "Jabuuti", + "DM": "Dominica", + "DO": "República Dominicana", + "EC": "Ecuador", + "EG": "مصر", + "SV": "El Salvador", + "GQ": "Guinea Ecuatorial", + "ER": "ኤርትራ", + "EE": "Eesti", + "ET": "ኢትዮጵያ", + "FK": "Falkland Islands", + "FO": "Føroyar", + "FJ": "Fiji", + "FI": "Suomi", + "FR": "France", + "GF": "Guyane française", + "PF": "Polynésie française", + "TF": "French Southern Territories", + "GA": "Gabon", + "GM": "Gambia", + "GE": "საქართველო", + "DE": "Deutschland", + "GH": "Gaana", + "GI": "Gibraltar", + "GR": "Ελλάδα", + "GL": "Kalaallit Nunaat", + "GD": "Grenada", + "GP": "Guadeloupe", + "GU": "Guam", + "GT": "Guatemala", + "GG": "Guernsey", + "GN": "Guinée", + "GW": "Guiné-Bissau", + "GY": "Guyana", + "HT": "Haïti", + "HM": "Heard Island and McDonald Islands", + "VA": "Città del Vaticano", + "HN": "Honduras", + "HK": "中國香港", + "HU": "Magyarország", + "IS": "Ísland", + "IN": "भारत", + "ID": "Indonesia", + "IR": "ایران", + "IQ": "العراق", + "IE": "Ireland", + "IM": "Isle of Man", + "IL": "ישראל", + "IT": "Italia", + "JM": "Jamaica", + "JP": "日本", + "JE": "Jersey", + "JO": "الأردن", + "KZ": "Казахстан", + "KE": "Kenya", + "KI": "Kiribati", + "KP": "조선민주주의인민공화국", + "KR": "대한민국", + "XK": "Kosovë", + "KW": "الكويت", + "KG": "Кыргызстан", + "LA": "ລາວ", + "LV": "Latvija", + "LB": "لبنان", + "LS": "Lesotho", + "LR": "Liberia", + "LY": "ليبيا", + "LI": "Liechtenstein", + "LT": "Lietuva", + "LU": "Luxembourg", + "MO": "中國澳門", + "MK": "Северна Македонија", + "MG": "Madagasikara", + "MW": "Malawi", + "MY": "Malaysia", + "MV": "Maldives", + "ML": "Mali", + "MT": "Malta", + "MH": "Marshall Islands", + "MQ": "Martinique", + "MR": "موريتانيا", + "MU": "Moris", + "YT": "Mayotte", + "MX": "México", + "FM": "Micronesia", + "MD": "Republica Moldova", + "MC": "Monaco", + "MN": "Монгол", + "ME": "Crna Gora", + "MS": "Montserrat", + "MA": "المغرب", + "MZ": "Moçambique", + "MM": "မြန်မာ", + "NA": "Namibië", + "NR": "Nauru", + "NP": "नेपाल", + "NL": "Nederland", + "NC": "Nouvelle-Calédonie", + "NZ": "New Zealand", + "NI": "Nicaragua", + "NE": "Nijar", + "NG": "Nigeria", + "NU": "Niue", + "NF": "Norfolk Island", + "MP": "Northern Mariana Islands", + "NO": "Norge", + "OM": "عُمان", + "PK": "پاکستان", + "PW": "Palau", + "PS": "الأراضي الفلسطينية", + "PA": "Panamá", + "PG": "Papua New Guinea", + "PY": "Paraguay", + "PE": "Perú", + "PH": "Philippines", + "PN": "Pitcairn Islands", + "PL": "Polska", + "PT": "Portugal", + "PR": "Puerto Rico", + "QA": "قطر", + "RE": "La Réunion", + "RO": "România", + "RU": "Россия", + "RW": "U Rwanda", + "BL": "Saint-Barthélemy", + "SH": "St. Helena", + "KN": "St. Kitts & Nevis", + "LC": "St. Lucia", + "MF": "Saint-Martin", + "PM": "Saint-Pierre-et-Miquelon", + "VC": "St. Vincent & Grenadines", + "WS": "Samoa", + "SM": "San Marino", + "ST": "São Tomé e Príncipe", + "SA": "المملكة العربية السعودية", + "SN": "Senegaal", + "RS": "Србија", + "SC": "Seychelles", + "SL": "Sierra Leone", + "SG": "Singapore", + "SX": "Sint Maarten", + "SK": "Slovensko", + "SI": "Slovenija", + "SB": "Solomon Islands", + "SO": "Soomaaliya", + "ZA": "South Africa", + "GS": "South Georgia and the South Sandwich Islands", + "SS": "جنوب السودان", + "ES": "España", + "LK": "ශ්\u200dරී ලංකාව", + "SD": "السودان", + "SR": "Suriname", + "SJ": "Svalbard og Jan Mayen", + "SZ": "Eswatini", + "SE": "Sverige", + "CH": "Schweiz", + "SY": "سوريا", + "TW": "台灣", + "TJ": "Тоҷикистон", + "TZ": "Tanzania", + "TH": "ไทย", + "TL": "Timor-Leste", + "TG": "Togo", + "TK": "Tokelau", + "TO": "Tonga", + "TT": "Trinidad & Tobago", + "TN": "تونس", + "TR": "Türkiye", + "TM": "Türkmenistan", + "TC": "Turks & Caicos Islands", + "TV": "Tuvalu", + "UG": "Uganda", + "UA": "Україна", + "AE": "الإمارات العربية المتحدة", + "GB": "United Kingdom", + "US": "United States", + "UM": "U.S. Outlying Islands", + "UY": "Uruguay", + "UZ": "Oʻzbekiston", + "VU": "Vanuatu", + "VE": "Venezuela", + "VN": "Việt Nam", + "VG": "British Virgin Islands", + "VI": "U.S. Virgin Islands", + "WF": "Wallis-et-Futuna", + "EH": "الصحراء الغربية", + "YE": "اليمن", + "ZM": "Zambia", + "ZW": "Zimbabwe", +} +LANGUAGE_NAMES = { + "ur": "اردو", + "kmr": "Kurdî (Tirkiye)", + "sw": "Kiswahili", + "pl": "Polski", + "vi": "Tiếng Việt", + "sq": "Shqip", + "sv": "Svenska", + "he": "עברית", + "da": "Dansk", + "pt_BR": "Português (Brasil)", + "ja": "日本語", + "el": "Ελληνικά", + "fy": "Frysk", + "it": "Italiano", + "ca": "Català", + "nb_NO": "Norsk Bokmål (Norge)", + "cs": "Čeština", + "pa_PK": "پنجابی (عربی, پاکستان)", + "te": "తెలుగు", + "pt_PT": "Português (Portugal)", + "zh_Hans": "中文 (简体)", + "ru": "Русский", + "tl": "Filipino (Pilipinas)", + "zh_Hant": "中文 (繁體)", + "ro": "Română", + "uk": "Українська", + "sr": "Српски", + "ar": "العربية", + "hu": "Magyar", + "nl": "Nederlands", + "bg": "Български", + "bn": "বাংলা", + "hi": "हिन्दी", + "de": "Deutsch", + "ko": "한국어", + "fi": "Suomi", + "eo": "Esperanto", + "id": "Indonesia", + "fr": "Français", + "es": "Español", + "et": "Eesti", + "en": "English", + "fa": "فارسی", + "cy": "Cymraeg", + "th": "ไทย", + "tr": "Türkçe", +} +TEXT_DIRECTIONS = { + "ur": "rtl", + "kmr": "ltr", + "sw": "ltr", + "pl": "ltr", + "vi": "ltr", + "sq": "ltr", + "sv": "ltr", + "he": "rtl", + "da": "ltr", + "pt_BR": "ltr", + "ja": "ltr", + "el": "ltr", + "fy": "ltr", + "it": "ltr", + "ca": "ltr", + "nb_NO": "ltr", + "cs": "ltr", + "pa_PK": "rtl", + "te": "ltr", + "pt_PT": "ltr", + "zh_Hans": "ltr", + "ru": "ltr", + "tl": "ltr", + "zh_Hant": "ltr", + "ro": "ltr", + "uk": "ltr", + "sr": "ltr", + "ar": "rtl", + "hu": "ltr", + "nl": "ltr", + "bg": "ltr", + "bn": "ltr", + "hi": "ltr", + "de": "ltr", + "ko": "ltr", + "fi": "ltr", + "eo": "ltr", + "id": "ltr", + "fr": "ltr", + "es": "ltr", + "et": "ltr", + "en": "ltr", + "fa": "rtl", + "cy": "ltr", + "th": "ltr", + "tr": "ltr", +} diff --git a/testddb.py b/testddb.py index 334b433eadc..53ca1e3ca11 100644 --- a/testddb.py +++ b/testddb.py @@ -1,12 +1,11 @@ - import pprint from website import dynamo, database storage = dynamo.AwsDynamoStorage.from_env() if not storage: - raise RuntimeError('DDB not configure quickly') + raise RuntimeError("DDB not configure quickly") -Q = dynamo.Table(storage, 'preferences', partition_key='id', sort_key='level') +Q = dynamo.Table(storage, "preferences", partition_key="id", sort_key="level") -recs = database.USERS.get({'username': 'rix0rrr'}) +recs = database.USERS.get({"username": "rix0rrr"}) pprint.pprint(recs) diff --git a/tests/Highlighter.py b/tests/Highlighter.py index 761198675d2..8fd22bb1d9c 100644 --- a/tests/Highlighter.py +++ b/tests/Highlighter.py @@ -5,18 +5,18 @@ # Transcription of the used tokens into a symbol (a letter) in order to apply a coloring TOKEN_CODE = { - "text": 'T', # normal - "keyword": 'K', - "comment": 'C', - "variable": 'N', - "constant.character": 'S', - "event": 'E', - "invalid": 'I', - "support.function": 'F' + "text": "T", # normal + "keyword": "K", + "comment": "C", + "variable": "N", + "constant.character": "S", + "event": "E", + "invalid": "I", + "support.function": "F", } # for the caractere SPACE, all this highlights are equals -SAME_COLOR_FOR_SPACE = ['T', 'K', 'C', 'N', 'S', 'E'] +SAME_COLOR_FOR_SPACE = ["T", "K", "C", "N", "S", "E"] ABBREVIATION = { "text": "T", @@ -24,53 +24,46 @@ "txt": "T", "white": "T", "T": "T", - "keyword": "K", "kw": "K", "red": "K", "K": "K", - "comment": "C", "cmt": "C", "grey": "C", "C": "C", - "variable": "N", "number": "N", "green": "N", "N": "N", - "constant.character": "S", "string": "S", "str": "S", "blue": "S", "S": "S", - "event": "E", "pressed": "E", "orange": "E", "E": "E", - "invalid": "I", "inv": "I", "pink": "I", "I": "I", - - "support.function": 'F', + "support.function": "F", } class HighlightTester(unittest.TestCase): - def assert_highlighted_chr( - self, - code, - expected, - level, - lang="en", - start_token="start", - last_state="start", - intermediate_tests=True): + self, + code, + expected, + level, + lang="en", + start_token="start", + last_state="start", + intermediate_tests=True, + ): """Test if the code has the expected coloring on one line Arguments : @@ -90,13 +83,14 @@ def assert_highlighted_chr( self.checkInter(code, expected, state_machine, start_token) def assert_highlighted_chr_multi_line( - self, - *args, - level, - lang="en", - start_token="start", - last_state="start", - intermediate_tests=True): + self, + *args, + level, + lang="en", + start_token="start", + last_state="start", + intermediate_tests=True, + ): """Test if the code has the expected coloring on several lines Arguments : @@ -115,29 +109,26 @@ def assert_highlighted_chr_multi_line( """ if len(args) % 2 != 0: raise RuntimeError( - f'Pass an even number of strings to assert_highlighted_chr_multi_line\ - (alternatingly code and highlighting). Got: {args}') + f"Pass an even number of strings to assert_highlighted_chr_multi_line\ + (alternatingly code and highlighting). Got: {args}" + ) - code = '\n'.join(line for i, line in enumerate(args) if i % 2 == 0) - expected = '\n'.join(line for i, line in enumerate(args) if i % 2 != 0) + code = "\n".join(line for i, line in enumerate(args) if i % 2 == 0) + expected = "\n".join(line for i, line in enumerate(args) if i % 2 != 0) self.assert_highlighted_chr( - code, - expected, - level, - lang, - start_token, - last_state, - intermediate_tests) + code, expected, level, lang, start_token, last_state, intermediate_tests + ) def assert_highlighted( - self, - code_coloration, - level, - lang="en", - start_token="start", - last_state="start", - intermediate_tests=True): + self, + code_coloration, + level, + lang="en", + start_token="start", + last_state="start", + intermediate_tests=True, + ): """Test if the code has the expected coloring on one line Arguments : @@ -152,22 +143,18 @@ def assert_highlighted( """ code, expected = self.convert(code_coloration) self.assert_highlighted_chr( - code, - expected, - level, - lang, - start_token, - last_state, - intermediate_tests) + code, expected, level, lang, start_token, last_state, intermediate_tests + ) def assert_highlighted_multi_line( - self, - *args, - level, - lang="en", - start_token="start", - last_state="start", - intermediate_tests=True): + self, + *args, + level, + lang="en", + start_token="start", + last_state="start", + intermediate_tests=True, + ): """Test if the code has the expected coloring on several lines Arguments : @@ -183,13 +170,8 @@ def assert_highlighted_multi_line( code_coloration = "\n".join(args) code, expected = self.convert(code_coloration) self.assert_highlighted_chr( - code, - expected, - level, - lang, - start_token, - last_state, - intermediate_tests) + code, expected, level, lang, start_token, last_state, intermediate_tests + ) def checkInter(self, code, expected, state_machine, start_token="start"): """Check the highlighting on the intermediate states @@ -210,7 +192,9 @@ def checkInter(self, code, expected, state_machine, start_token="start"): for n in range(len(listCode)): self.check(listCode[n], listExpected[n], state_machine, start_token, None) - def check(self, code, expected, state_machine, start_token="start", last_state='start'): + def check( + self, code, expected, state_machine, start_token="start", last_state="start" + ): """Apply state_machine on code and check if the result is valid Arguments : @@ -267,20 +251,20 @@ def get_state_machine(self, level, lang="en"): Returns a state machine. """ root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) - trad_file = os.path.normpath(root_dir + '/highlighting/highlighting-trad.json') + trad_file = os.path.normpath(root_dir + "/highlighting/highlighting-trad.json") # get traduction - with open(trad_file, 'r', encoding='utf-8') as file_regex_trad: + with open(trad_file, "r", encoding="utf-8") as file_regex_trad: data_regex_trad = json.load(file_regex_trad) if lang not in data_regex_trad.keys(): - lang = 'en' + lang = "en" regex_trad = data_regex_trad[lang] # get state_machine - sm_file = os.path.normpath(root_dir + '/highlighting/highlighting.json') - with open(sm_file, 'r', encoding='utf-8') as file_regex: + sm_file = os.path.normpath(root_dir + "/highlighting/highlighting.json") + with open(sm_file, "r", encoding="utf-8") as file_regex: data_regex = json.load(file_regex) # apply translation of keywords @@ -288,14 +272,18 @@ def get_state_machine(self, level, lang="en"): for state in lvl_rules["rules"]: for rule in lvl_rules["rules"][state]: for key in regex_trad: - rule['regex'] = rule['regex'].replace("__" + key + "__", regex_trad[key]) + rule["regex"] = rule["regex"].replace( + "__" + key + "__", regex_trad[key] + ) # get state_machine for the level - state_machine = [item for item in data_regex if item['name'] == level] + state_machine = [item for item in data_regex if item["name"] == level] if len(state_machine) != 1: - raise RuntimeError(f'Expected exactly 1 rule with {level}, got {len(state_machine)}') + raise RuntimeError( + f"Expected exactly 1 rule with {level}, got {len(state_machine)}" + ) else: - state_machine = state_machine[0]['rules'] + state_machine = state_machine[0]["rules"] return state_machine def convert(self, code_coloration): @@ -331,7 +319,6 @@ def convert(self, code_coloration): state = KEYWORD else: # not a special symbol - if state == CODE: tmp_code.append(ch) elif state == KEYWORD: @@ -353,7 +340,7 @@ def genInterTest(code, expected): to check that the highlighting when typing the code will be consistent.""" C, E = [], [] - codes = [k.split(' ') for k in code.split("\n")] + codes = [k.split(" ") for k in code.split("\n")] currentCode = [] @@ -366,7 +353,7 @@ def genInterTest(code, expected): # add current = "\n".join([" ".join(k) for k in currentCode + [currentLine]]) C.append(current) - E.append(expected[:len(current)]) + E.append(expected[: len(current)]) currentCode.append(currentLine) @@ -374,7 +361,6 @@ def genInterTest(code, expected): class SimulatorAce: - # constructor of SimulatorAce with check on state_machine def __init__(self, state_machine): self.state_machine = state_machine @@ -403,28 +389,30 @@ def _precompile_regexes(self): """ for state in self.state_machine: for rule in self.state_machine[state]: - if "token" not in rule: raise ValueError("We need a token in all rules !") - rule["regex_compile"] = re.compile(rule['regex'], re.MULTILINE) + rule["regex_compile"] = re.compile(rule["regex"], re.MULTILINE) rule["nb_groups"] = rule["regex_compile"].groups if rule["nb_groups"] == 0: if not isinstance(rule["token"], str): raise ValueError( - f"if regex has no groups, token must be a string. In this rule : {rule}!") + f"if regex has no groups, token must be a string. In this rule : {rule}!" + ) else: if not isinstance(rule["token"], list): raise ValueError( - f"if regex has groups, token must be a list. In this rule : {rule}!") + f"if regex has groups, token must be a list. In this rule : {rule}!" + ) else: if rule["nb_groups"] != len(rule["token"]): raise ValueError( f"The number of groups in the regex is different from the number of tokens.\ - In this rule : {rule}!") + In this rule : {rule}!" + ) def highlight(self, code, start_token="start"): """Simulates the application of syntax highlighting state_machine on a code. @@ -465,11 +453,15 @@ def highlight_rules_line(self, code, start_token="start"): default_token = "text" - find_transition, next_transition = self.find_match(code, current_position, current_state) + find_transition, next_transition = self.find_match( + code, current_position, current_state + ) while find_transition: - # get match - current_rule, current_match = next_transition["rule"], next_transition["match"] + current_rule, current_match = ( + next_transition["rule"], + next_transition["match"], + ) # we color the characters since the last match with the default coloring for c in range(current_position, current_match.start()): @@ -483,7 +475,7 @@ def highlight_rules_line(self, code, start_token="start"): # if rule has only one groups if current_rule["nb_groups"] == 0: - tok = current_rule['token'] + tok = current_rule["token"] length = current_match.end() - current_match.start() output.append(TOKEN_CODE[tok] * length) @@ -491,21 +483,22 @@ def highlight_rules_line(self, code, start_token="start"): else: pos = current_match.start() for i, submatch in enumerate(current_match.groups()): - tok = current_rule['token'][i % len(current_rule['token'])] + tok = current_rule["token"][i % len(current_rule["token"])] output.append(TOKEN_CODE[tok] * len(submatch)) pos += len(submatch) # get nexts values current_position = current_match.end() - if 'next' in current_rule: - current_state = current_rule['next'] + if "next" in current_rule: + current_state = current_rule["next"] if current_position == len(code): find_transition = False else: find_transition, next_transition = self.find_match( - code, current_position, current_state) + code, current_position, current_state + ) # we color the last characters since the last match with the default coloring for c in range(current_position, len(code)): @@ -514,7 +507,7 @@ def highlight_rules_line(self, code, start_token="start"): return "".join(output), current_state def find_match(self, code, current_position, current_state): - """ Find the next matching rule in the given code. + """Find the next matching rule in the given code. If there are multiple rules that match the code in the given state, returns the one that matches earliest in the source string. @@ -535,7 +528,6 @@ def find_match(self, code, current_position, current_state): next_pos = len(code) + 1 for rule in self.state_machine[current_state]: - regex_compile = rule["regex_compile"] match = regex_compile.search(code, current_position) diff --git a/tests/Tester.py b/tests/Tester.py index 00484050630..1579774ae98 100644 --- a/tests/Tester.py +++ b/tests/Tester.py @@ -17,7 +17,16 @@ class Snippet: - def __init__(self, filename, level, code, field_name=None, adventure_name=None, error=None, language=None): + def __init__( + self, + filename, + level, + code, + field_name=None, + adventure_name=None, + error=None, + language=None, + ): self.filename = filename self.level = level self.field_name = field_name @@ -29,12 +38,12 @@ def __init__(self, filename, level, code, field_name=None, adventure_name=None, else: self.language = language self.adventure_name = adventure_name - self.name = f'{self.language}-{self.level}-{self.field_name}' + self.name = f"{self.language}-{self.level}-{self.field_name}" self.hash = md5digest(self.code) class SkippedMapping: - """ Class used to test if a certain source mapping contains an exception type """ + """Class used to test if a certain source mapping contains an exception type""" def __init__(self, source_range: SourceRange, exception_type: type(Exception)): self.source_range = source_range @@ -42,56 +51,62 @@ def __init__(self, source_range: SourceRange, exception_type: type(Exception)): class HedyTester(unittest.TestCase): - level = None - equality_comparison_with_is = ['is', '='] - equality_comparison_commands = ['==', '='] - number_comparison_commands = ['>', '>=', '<', '<='] - comparison_commands = number_comparison_commands + ['!='] - arithmetic_operations = ['+', '-', '*', '/'] + equality_comparison_with_is = ["is", "="] + equality_comparison_commands = ["==", "="] + number_comparison_commands = [">", ">=", "<", "<="] + comparison_commands = number_comparison_commands + ["!="] + arithmetic_operations = ["+", "-", "*", "/"] quotes = ["'", '"'] - commands_level_4 = [("print 'hello'", "print(f'hello')"), - ("name is ask 'who?'", "name = input(f'who?')"), - ('name is Harry', "name = 'Harry'")] + commands_level_4 = [ + ("print 'hello'", "print(f'hello')"), + ("name is ask 'who?'", "name = input(f'who?')"), + ("name is Harry", "name = 'Harry'"), + ] @classmethod def setUpClass(cls): ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - directory = os.path.join(ROOT_DIR, 'grammars') + directory = os.path.join(ROOT_DIR, "grammars") - files_affecting_parsing = ( - [os.path.join(directory, filename) for filename in os.listdir(directory)] + - [os.path.join(ROOT_DIR, 'hedy.py')] - ) + files_affecting_parsing = [ + os.path.join(directory, filename) for filename in os.listdir(directory) + ] + [os.path.join(ROOT_DIR, "hedy.py")] # Sort these files so that the order is consistent between all platforms (this affects the hash!) files_affecting_parsing.sort() files_contents = [] for filename in files_affecting_parsing: - with open(filename, 'r', encoding='utf-8', newline='\n') as f: + with open(filename, "r", encoding="utf-8", newline="\n") as f: contents = f.read() files_contents.append(contents) - all_language_texts = '\n|\n'.join(files_contents) + all_language_texts = "\n|\n".join(files_contents) cls.all_language_texts = all_language_texts - cls.snippet_hashes = get_list_from_pickle(ROOT_DIR + '/all_snippet_hashes.pkl') + cls.snippet_hashes = get_list_from_pickle(ROOT_DIR + "/all_snippet_hashes.pkl") cls.snippet_hashes_original_len = len(cls.snippet_hashes) @classmethod def tearDownClass(cls): ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Only write file if we added any hashes to it and the env var is set - if os.getenv('save_snippet_hashes') and cls.snippet_hashes_original_len != len(cls.snippet_hashes): - with open(ROOT_DIR + '/all_snippet_hashes.pkl', 'wb') as f: + if os.getenv("save_snippet_hashes") and cls.snippet_hashes_original_len != len( + cls.snippet_hashes + ): + with open(ROOT_DIR + "/all_snippet_hashes.pkl", "wb") as f: pickle.dump(cls.snippet_hashes, f) def snippet_already_tested_with_current_hedy_version(self, snippet, level): try: - hash_language_plus_snippet_and_level = self.create_hash(self.all_language_texts, snippet, level) + hash_language_plus_snippet_and_level = self.create_hash( + self.all_language_texts, snippet, level + ) return hash_language_plus_snippet_and_level in self.snippet_hashes - except UnicodeEncodeError: # some tests (generated by Hypothesis) can't be hashed + except ( + UnicodeEncodeError + ): # some tests (generated by Hypothesis) can't be hashed return False @staticmethod @@ -113,18 +128,22 @@ def run_code(parse_result): code += utils.TURTLE_PREFIX_CODE if parse_result.has_pygame: pygame_test_prefix = ( - 'import os\n' - 'os.environ["SDL_VIDEODRIVER"] = "dummy" # No real image drivers exist, set to dummy for testing\n' - 'os.environ["SDL_AUDIODRIVER"] = "disk" # No real audio drivers exist, set to disk for testing\n' - ) + utils.PYGAME_PREFIX_CODE + ( - "pygame_end = True # Set to True so that we don't get stuck in a loop during testing\n" + ( + "import os\n" + 'os.environ["SDL_VIDEODRIVER"] = "dummy" # No real image drivers exist, set to dummy for testing\n' + 'os.environ["SDL_AUDIODRIVER"] = "disk" # No real audio drivers exist, set to disk for testing\n' + ) + + utils.PYGAME_PREFIX_CODE + + ( + "pygame_end = True # Set to True so that we don't get stuck in a loop during testing\n" + ) ) code += pygame_test_prefix code += parse_result.code # remove sleep comments to make program execution less slow - code = re.sub(r'time\.sleep\([^\n]*\)', 'pass', code) + code = re.sub(r"time\.sleep\([^\n]*\)", "pass", code) with HedyTester.captured_output() as (out, err): exec(code) @@ -134,16 +153,16 @@ def name(self): return inspect.stack()[1][3] def is_not_turtle(self): - return (lambda result: not result.has_turtle) + return lambda result: not result.has_turtle def is_turtle(self): - return (lambda result: result.has_turtle) + return lambda result: result.has_turtle def result_in(self, list): - return (lambda result: HedyTester.run_code(result) in list) + return lambda result: HedyTester.run_code(result) in list def exception_command(self, command): - return lambda c: c.exception.arguments['command'] == command + return lambda c: c.exception.arguments["command"] == command @staticmethod def as_list_of_tuples(*args): @@ -159,38 +178,39 @@ def as_list_of_tuples(*args): def codeToInvalidInfo(self, code): instance = hedy.IsValid() instance.level = self.level - program_root = hedy.parse_input(code, self.level, 'en') + program_root = hedy.parse_input(code, self.level, "en") is_valid = instance.transform(program_root) _, invalid_info = is_valid return invalid_info[0].line, invalid_info[0].column def multi_level_tester( - self, - code, - max_level=hedy.HEDY_MAX_LEVEL, - expected=None, - exception=None, - skipped_mappings: list[SkippedMapping] = None, - extra_check_function=None, - expected_commands=None, - lang='en', - translate=True, - output=None): + self, + code, + max_level=hedy.HEDY_MAX_LEVEL, + expected=None, + exception=None, + skipped_mappings: list[SkippedMapping] = None, + extra_check_function=None, + expected_commands=None, + lang="en", + translate=True, + output=None, + ): # used to test the same code snippet over multiple levels # Use exception to check for an exception if max_level < self.level: - raise Exception('Level too low!') + raise Exception("Level too low!") # ensure we never test levels above the max (useful for debugging) max_level = min(max_level, hedy.HEDY_MAX_LEVEL) # make it clear in the output this is a multilevel tester - print('\n\n\n') - print('-----------------') - print('Multi-level test!') - print('\n') + print("\n\n\n") + print("-----------------") + print("Multi-level test!") + print("\n") # Or use expect to check for an expected Python program # In the second case, you can also pass an extra function to check @@ -205,28 +225,34 @@ def multi_level_tester( expected_commands=expected_commands, lang=lang, translate=translate, - output=output) - print(f'Passed for level {level}') + output=output, + ) + print(f"Passed for level {level}") def single_level_tester( - self, - code, - level=None, - exception=None, - skipped_mappings: list[SkippedMapping] = None, - expected=None, - extra_check_function=None, - output=None, - expected_commands=None, - lang='en', - translate=True): - if level is None: # no level set (from the multi-tester)? grap current level from class + self, + code, + level=None, + exception=None, + skipped_mappings: list[SkippedMapping] = None, + expected=None, + extra_check_function=None, + output=None, + expected_commands=None, + lang="en", + translate=True, + ): + if ( + level is None + ): # no level set (from the multi-tester)? grap current level from class level = self.level if not self.snippet_already_tested_with_current_hedy_version(code, level): if skipped_mappings is not None: result = hedy.transpile(code, level, lang, skip_faulty=True) for skipped in skipped_mappings: - result_error = result.source_map.get_error_from_hedy_source_range(skipped.source_range) + result_error = result.source_map.get_error_from_hedy_source_range( + skipped.source_range + ) self.assertEqual(expected, result.code) self.assertEqual(type(result_error), skipped.exception_type) if extra_check_function is not None: @@ -243,56 +269,77 @@ def single_level_tester( self.assertEqual(expected, result.code) if translate: - if lang == 'en': # if it is English + if lang == "en": # if it is English # and if the code transpiles (evidenced by the fact that we reach this # line) we should be able to translate too # TODO FH Feb 2022: we pick Dutch here not really fair or good practice :D # Maybe we should do a random language? in_dutch = hedy_translation.translate_keywords( - code, from_lang=lang, to_lang="nl", level=self.level) + code, from_lang=lang, to_lang="nl", level=self.level + ) back_in_english = hedy_translation.translate_keywords( - in_dutch, from_lang="nl", to_lang=lang, level=self.level).strip() + in_dutch, from_lang="nl", to_lang=lang, level=self.level + ).strip() self.assert_translated_code_equal(code, back_in_english) else: # not English? translate to it and back! in_english = hedy_translation.translate_keywords( - code, from_lang=lang, to_lang="en", level=self.level) + code, from_lang=lang, to_lang="en", level=self.level + ) back_in_org = hedy_translation.translate_keywords( - in_english, from_lang="en", to_lang=lang, level=self.level) + in_english, + from_lang="en", + to_lang=lang, + level=self.level, + ) self.assert_translated_code_equal(code, back_in_org) all_commands = hedy.all_commands(code, level, lang) if expected_commands is not None: self.assertEqual(expected_commands, all_commands) # <- use this to run tests locally with unittest - if ('ask' not in all_commands) and ('input' not in all_commands) and ('clear' not in all_commands): + if ( + ("ask" not in all_commands) + and ("input" not in all_commands) + and ("clear" not in all_commands) + ): self.assertTrue(self.validate_Python_code(result)) if output is not None: - if extra_check_function is None: # most programs have no turtle so make that the default + if ( + extra_check_function is None + ): # most programs have no turtle so make that the default extra_check_function = self.is_not_turtle() self.assertEqual(output, HedyTester.run_code(result)) self.assertTrue(extra_check_function(result)) # all ok? -> save hash! - self.snippet_hashes.add(self.create_hash(self.all_language_texts, code, level)) + self.snippet_hashes.add( + self.create_hash(self.all_language_texts, code, level) + ) def source_map_tester(self, code, expected_source_map: dict): - result = hedy.transpile(code, self.level, 'en') - self.assertDictEqual(result.source_map.get_compressed_mapping(), expected_source_map) + result = hedy.transpile(code, self.level, "en") + self.assertDictEqual( + result.source_map.get_compressed_mapping(), expected_source_map + ) def assert_translated_code_equal(self, orignal, translation): # When we translate a program we lose information about the whitespaces of the original program. # So when comparing the original and the translated code, we compress multiple whitespaces into one. - self.assertEqual(re.sub('\\s+', ' ', orignal), re.sub('\\s+', ' ', translation)) + self.assertEqual(re.sub("\\s+", " ", orignal), re.sub("\\s+", " ", translation)) @staticmethod def validate_Python_code(parseresult): # Code used in the Adventure and Level Defaults tester to validate Hedy code try: - if not parseresult.has_turtle and not parseresult.has_pygame: # ouput from turtle or pygame cannot be captured + if ( + not parseresult.has_turtle and not parseresult.has_pygame + ): # ouput from turtle or pygame cannot be captured HedyTester.run_code(parseresult) - except hedy.exceptions.CodePlaceholdersPresentException: # Code with blanks is allowed + except ( + hedy.exceptions.CodePlaceholdersPresentException + ): # Code with blanks is allowed pass except OSError: return True # programs with ask cannot be tested with output :( @@ -304,49 +351,56 @@ def validate_Python_code(parseresult): # The followings methods abstract the specifics of the tranpilation and keep tests succinct @staticmethod def forward_transpiled(val, level): - return HedyTester.turtle_command_transpiled('forward', val, level) + return HedyTester.turtle_command_transpiled("forward", val, level) @staticmethod def turn_transpiled(val, level): - return HedyTester.turtle_command_transpiled('right', val, level) + return HedyTester.turtle_command_transpiled("right", val, level) @staticmethod def turtle_command_transpiled(command, val, level): - command_text = 'turn' - suffix = '' - if command == 'forward': - command_text = 'forward' - suffix = '\n time.sleep(0.1)' + command_text = "turn" + suffix = "" + if command == "forward": + command_text = "forward" + suffix = "\n time.sleep(0.1)" - type = 'int' if level < 12 else 'float' + type = "int" if level < 12 else "float" - return textwrap.dedent(f"""\ + return textwrap.dedent( + f"""\ __trtl = {val} try: __trtl = {type}(__trtl) except ValueError: raise Exception(f'While running your program the command {command_text} received the value {{__trtl}} which is not allowed. Try changing the value to a number.') - t.{command}(min(600, __trtl) if __trtl > 0 else max(-600, __trtl)){suffix}""") + t.{command}(min(600, __trtl) if __trtl > 0 else max(-600, __trtl)){suffix}""" + ) @staticmethod def sleep_command_transpiled(val): - return textwrap.dedent(f"""\ + return textwrap.dedent( + f"""\ try: time.sleep(int({val})) except ValueError: - raise Exception(f'While running your program the command sleep received the value {{{val}}} which is not allowed. Try changing the value to a number.')""") + raise Exception(f'While running your program the command sleep received the value {{{val}}} which is not allowed. Try changing the value to a number.')""" + ) @staticmethod def turtle_color_command_transpiled(val): - return textwrap.dedent(f"""\ + return textwrap.dedent( + f"""\ __trtl = f'{val}' if __trtl not in ['black', 'blue', 'brown', 'gray', 'green', 'orange', 'pink', 'purple', 'red', 'white', 'yellow']: raise Exception(f'While running your program the command color received the value {{__trtl}} which is not allowed. Try using another color.') - t.pencolor(__trtl)""") + t.pencolor(__trtl)""" + ) @staticmethod def input_transpiled(var_name, text): - return textwrap.dedent(f"""\ + return textwrap.dedent( + f"""\ {var_name} = input(f'''{text}''') try: {var_name} = int({var_name}) @@ -354,39 +408,54 @@ def input_transpiled(var_name, text): try: {var_name} = float({var_name}) except ValueError: - pass""") + pass""" + ) @staticmethod def remove_transpiled(list_name, value): - return textwrap.dedent(f"""\ + return textwrap.dedent( + f"""\ try: {list_name}.remove({value}) except: - pass""") + pass""" + ) @staticmethod def list_access_transpiled(list_access): - return textwrap.dedent(f"""\ + return textwrap.dedent( + f"""\ try: {list_access} except IndexError: - raise Exception('catch_index_exception')""") + raise Exception('catch_index_exception')""" + ) # Used to overcome indentation issues when the above code is inserted # in test cases which use different indentation style (e.g. 2 or 4 spaces) @staticmethod def dedent(*args): - return '\n'.join([textwrap.indent(textwrap.dedent(a[0]), a[1]) if isinstance(a, tuple) else textwrap.dedent(a) - for a in args]) + return "\n".join( + [ + textwrap.indent(textwrap.dedent(a[0]), a[1]) + if isinstance(a, tuple) + else textwrap.dedent(a) + for a in args + ] + ) @staticmethod def indent(code, spaces_amount=2, skip_first_line=False): - lines = code.split('\n') + lines = code.split("\n") if not skip_first_line: - return '\n'.join([' ' * spaces_amount + line for line in lines]) + return "\n".join([" " * spaces_amount + line for line in lines]) else: - return lines[0] + '\n' + '\n'.join([' ' * spaces_amount + line for line in lines[1::]]) + return ( + lines[0] + + "\n" + + "\n".join([" " * spaces_amount + line for line in lines[1::]]) + ) @staticmethod def translate_keywords_in_snippets(snippets): @@ -397,7 +466,7 @@ def translate_keywords_in_snippets(snippets): for k, v in keyword_dict[lang].items(): if isinstance(v, str) and "|" in v: # when we have several options, pick the first one as default - keyword_dict[lang][k] = v.split('|')[0] + keyword_dict[lang][k] = v.split("|")[0] english_keywords = KEYWORDS.get("en") # We replace the code snippet placeholders with actual keywords to the code is valid: {print} -> print @@ -405,14 +474,18 @@ def translate_keywords_in_snippets(snippets): for snippet in snippets: try: if snippet[1].language in ALL_KEYWORD_LANGUAGES.keys(): - snippet[1].code = snippet[1].code.format(**keyword_dict[snippet[1].language]) + snippet[1].code = snippet[1].code.format( + **keyword_dict[snippet[1].language] + ) else: snippet[1].code = snippet[1].code.format(**english_keywords) except KeyError: print("This following snippet contains an invalid placeholder...") print(snippet) except ValueError: - print("This following snippet contains an unclosed invalid placeholder...") + print( + "This following snippet contains an unclosed invalid placeholder..." + ) print(snippet) return snippets @@ -420,30 +493,30 @@ def translate_keywords_in_snippets(snippets): def create_hash(self, hedy_language, snippet, level): try: t = snippet + "|\n" + str(level) + "|\n" + hedy_language - return hashlib.md5(t.encode('utf-8')).hexdigest() + return hashlib.md5(t.encode("utf-8")).hexdigest() except UnicodeEncodeError: # some tests can't be hashed - return '' + return "" def get_list_from_pickle(filename): try: - with open(filename, 'rb') as f: + with open(filename, "rb") as f: snippet_hashes = pickle.load(f) except FileNotFoundError: # non existent file snippet_hashes = set() - with open(filename, 'wb') as f: + with open(filename, "wb") as f: pickle.dump(snippet_hashes, f) except pickle.UnpicklingError: snippet_hashes = set() - with open(filename, 'wb') as f: + with open(filename, "wb") as f: pickle.dump(snippet_hashes, f) except EOFError: snippet_hashes = set() - with open(filename, 'wb') as f: + with open(filename, "wb") as f: pickle.dump(snippet_hashes, f) return snippet_hashes def md5digest(x): - return hashlib.md5(x.encode('utf-8')).hexdigest() + return hashlib.md5(x.encode("utf-8")).hexdigest() diff --git a/tests/test_abproxying.py b/tests/test_abproxying.py index ea6ae5136db..3249d5f95f2 100644 --- a/tests/test_abproxying.py +++ b/tests/test_abproxying.py @@ -5,20 +5,30 @@ class TestAbTesting(unittest.TestCase): def test_extract_cookies(self): - set_cookie = 'session=eyJzZXNzaW9uX2lkIjoiODgwOGMxMjIxMmVmNGM5NjkzNTFhMWYxYzEyNGExNjUiLCJ2YWx1ZSI6IjEyMyJ9.'\ - 'YLOIMA.b71v_iV4DgyniHlpuyzdVJBkM5M; Secure; HttpOnly; Path=/; SameSite=Lax' - session = ab_proxying.extract_session_from_cookie(set_cookie, 'TheSecret') - self.assertEqual(session, { - 'session_id': '8808c12212ef4c969351a1f1c124a165', - 'value': '123', - }) + set_cookie = ( + "session=eyJzZXNzaW9uX2lkIjoiODgwOGMxMjIxMmVmNGM5NjkzNTFhMWYxYzEyNGExNjUiLCJ2YWx1ZSI6IjEyMyJ9." + "YLOIMA.b71v_iV4DgyniHlpuyzdVJBkM5M; Secure; HttpOnly; Path=/; SameSite=Lax" + ) + session = ab_proxying.extract_session_from_cookie(set_cookie, "TheSecret") + self.assertEqual( + session, + { + "session_id": "8808c12212ef4c969351a1f1c124a165", + "value": "123", + }, + ) def test_extract_cookies_with_invalid_secret(self): """Test that we successfully decode the cookies, even if the secret key is wrong.""" - set_cookie = 'session=eyJzZXNzaW9uX2lkIjoiODgwOGMxMjIxMmVmNGM5NjkzNTFhMWYxYzEyNGExNjUiLCJ2YWx1ZSI6IjEyMyJ9.YLO'\ - 'IMA.b71v_iV4DgyniHlpuyzdVJBkM5M; Secure; HttpOnly; Path=/; SameSite=Lax' - session = ab_proxying.extract_session_from_cookie(set_cookie, 'Banana') - self.assertEqual(session, { - 'session_id': '8808c12212ef4c969351a1f1c124a165', - 'value': '123', - }) + set_cookie = ( + "session=eyJzZXNzaW9uX2lkIjoiODgwOGMxMjIxMmVmNGM5NjkzNTFhMWYxYzEyNGExNjUiLCJ2YWx1ZSI6IjEyMyJ9.YLO" + "IMA.b71v_iV4DgyniHlpuyzdVJBkM5M; Secure; HttpOnly; Path=/; SameSite=Lax" + ) + session = ab_proxying.extract_session_from_cookie(set_cookie, "Banana") + self.assertEqual( + session, + { + "session_id": "8808c12212ef4c969351a1f1c124a165", + "value": "123", + }, + ) diff --git a/tests/test_distance_algo.py b/tests/test_distance_algo.py index 6e7254107d8..f4e798b19da 100644 --- a/tests/test_distance_algo.py +++ b/tests/test_distance_algo.py @@ -6,121 +6,122 @@ class TestsKeywordSuggestions(unittest.TestCase): - def test_self_command_print(self): invalid_command = "print" - keywords_en_level_1 = hedy.get_suggestions_for_language('en', 1) + keywords_en_level_1 = hedy.get_suggestions_for_language("en", 1) closest = hedy.closest_command(invalid_command, keywords_en_level_1) - self.assertEqual('keyword', closest) + self.assertEqual("keyword", closest) def test_self_command_ask(self): invalid_command = "ask" - keywords_en_level_1 = hedy.get_suggestions_for_language('en', 1) + keywords_en_level_1 = hedy.get_suggestions_for_language("en", 1) closest = hedy.closest_command(invalid_command, keywords_en_level_1) - self.assertEqual('keyword', closest) + self.assertEqual("keyword", closest) def test_self_command_echo(self): invalid_command = "echo" - keywords_en_level_1 = hedy.get_suggestions_for_language('en', 1) + keywords_en_level_1 = hedy.get_suggestions_for_language("en", 1) closest = hedy.closest_command(invalid_command, keywords_en_level_1) - self.assertEqual('keyword', closest) + self.assertEqual("keyword", closest) def test_print_difference_1(self): invalid_command = "pront" - keywords_en_level_1 = hedy.get_suggestions_for_language('en', 1) + keywords_en_level_1 = hedy.get_suggestions_for_language("en", 1) closest = hedy.closest_command(invalid_command, keywords_en_level_1) - self.assertEqual('print', closest) + self.assertEqual("print", closest) def test_print_difference_2(self): invalid_command = "prond" - keywords_en_level_1 = hedy.get_suggestions_for_language('en', 1) + keywords_en_level_1 = hedy.get_suggestions_for_language("en", 1) closest = hedy.closest_command(invalid_command, keywords_en_level_1) - self.assertEqual('print', closest) + self.assertEqual("print", closest) def test_echo_command_1(self): invalid_command = "echoo" - keywords_en_level_1 = hedy.get_suggestions_for_language('en', 1) + keywords_en_level_1 = hedy.get_suggestions_for_language("en", 1) closest = hedy.closest_command(invalid_command, keywords_en_level_1) - self.assertEqual('echo', closest) + self.assertEqual("echo", closest) def test_echo_command_2(self): invalid_command = "ego" - keywords_en_level_1 = hedy.get_suggestions_for_language('en', 1) + keywords_en_level_1 = hedy.get_suggestions_for_language("en", 1) closest = hedy.closest_command(invalid_command, keywords_en_level_1) - self.assertEqual('echo', closest) + self.assertEqual("echo", closest) def test_echo_command_3(self): invalid_command = "eechooooooo" - keywords_en_level_1 = hedy.get_suggestions_for_language('en', 1) + keywords_en_level_1 = hedy.get_suggestions_for_language("en", 1) closest = hedy.closest_command(invalid_command, keywords_en_level_1) self.assertEqual(None, closest) def test_ask_command_nl(self): invalid_command = "ask" - keywords_nl_level_1 = hedy.get_suggestions_for_language('nl', 1) + keywords_nl_level_1 = hedy.get_suggestions_for_language("nl", 1) closest = hedy.closest_command(invalid_command, keywords_nl_level_1) - self.assertEqual('keyword', closest) + self.assertEqual("keyword", closest) - @parameterized.expand([ - (1, 'print', 'pnirt'), - (1, 'turn', 'tnru'), - (1, 'turn', 'purn'), - (3, 'sleep', 'sleepb'), - (3, 'at', 'mt'), - (4, 'ask', 'sk'), - (4, 'print', 'prtni'), - (4, 'random', 'nandom'), - (4, 'add', 'addg'), - (4, 'print', 'pridnt'), - (5, 'add', 'wdd'), - (5, 'else', 'elyse'), - (5, 'else', 'esle'), - (5, 'forward', 'fowrard'), - (5, 'forward', 'fwroard'), - (5, 'random', 'arndom'), - (6, 'print', 'pritn'), - (6, 'add', 'dda'), - (6, 'else', 'eles'), - (6, 'remove', 'zremove'), - (6, 'turn', 'tunr'), - (7, 'print', 'pxrint'), - (7, 'print', 'pyrint'), - (7, 'random', 'radnom'), - (7, 'times', 'tiems'), - (8, 'ask', 'abk'), - (8, 'remove', 'reomve'), - (8, 'remove', 'rmeove'), - (8, 'repeat', 'repceat'), - (9, 'from', 'rfom'), - (9, 'remove', 'reove'), - (9, 'times', 'timers'), - (10, 'add', 'ajdd'), - (11, 'at', 'rt'), - (11, 'for', 'or'), - (11, 'random', 'raodnm'), - (12, 'in', 'inm'), - (12, 'print', 'rint'), - (12, 'sleep', 'suleep'), - (13, 'else', 'lese'), - (14, 'remove', 'remoe'), - (14, 'add', 'dadd'), - (14, 'range', 'raige'), - (15, 'and', 'asnd'), - (15, 'random', 'rakdom'), - (15, 'else', 'ese'), - (15, 'print', 'irpnt'), - (15, 'print', 'pribnt'), - (15, 'random', 'randoo'), - (15, 'while', 'whcle'), - (16, 'print', 'prnit'), - (16, 'remove', 'emove'), - (16, 'while', 'whilee'), - (17, 'sleep', 'slkep'), - (18, 'or', 'oru'), - (18, 'sleep', 'slvep'), - (18, 'print', 'prinbt'), - ]) + @parameterized.expand( + [ + (1, "print", "pnirt"), + (1, "turn", "tnru"), + (1, "turn", "purn"), + (3, "sleep", "sleepb"), + (3, "at", "mt"), + (4, "ask", "sk"), + (4, "print", "prtni"), + (4, "random", "nandom"), + (4, "add", "addg"), + (4, "print", "pridnt"), + (5, "add", "wdd"), + (5, "else", "elyse"), + (5, "else", "esle"), + (5, "forward", "fowrard"), + (5, "forward", "fwroard"), + (5, "random", "arndom"), + (6, "print", "pritn"), + (6, "add", "dda"), + (6, "else", "eles"), + (6, "remove", "zremove"), + (6, "turn", "tunr"), + (7, "print", "pxrint"), + (7, "print", "pyrint"), + (7, "random", "radnom"), + (7, "times", "tiems"), + (8, "ask", "abk"), + (8, "remove", "reomve"), + (8, "remove", "rmeove"), + (8, "repeat", "repceat"), + (9, "from", "rfom"), + (9, "remove", "reove"), + (9, "times", "timers"), + (10, "add", "ajdd"), + (11, "at", "rt"), + (11, "for", "or"), + (11, "random", "raodnm"), + (12, "in", "inm"), + (12, "print", "rint"), + (12, "sleep", "suleep"), + (13, "else", "lese"), + (14, "remove", "remoe"), + (14, "add", "dadd"), + (14, "range", "raige"), + (15, "and", "asnd"), + (15, "random", "rakdom"), + (15, "else", "ese"), + (15, "print", "irpnt"), + (15, "print", "pribnt"), + (15, "random", "randoo"), + (15, "while", "whcle"), + (16, "print", "prnit"), + (16, "remove", "emove"), + (16, "while", "whilee"), + (17, "sleep", "slkep"), + (18, "or", "oru"), + (18, "sleep", "slvep"), + (18, "print", "prinbt"), + ] + ) def test_command_en(self, level, correct, mistake): - keywords = hedy.get_suggestions_for_language('en', level) + keywords = hedy.get_suggestions_for_language("en", level) closest = hedy.closest_command(mistake, keywords) self.assertEqual(correct, closest) diff --git a/tests/test_dynamo.py b/tests/test_dynamo.py index da6303bd5c1..d1038f8f8c8 100644 --- a/tests/test_dynamo.py +++ b/tests/test_dynamo.py @@ -17,9 +17,10 @@ def insert(self, *rows): def insert_sample_data(self): self.insert( - dict(id='key', sort=1, x=1, y=1, m=9), - dict(id='key', sort=2, x=1, y=3, m=9), - dict(id='key', sort=3, x=1, y=2, m=8)) + dict(id="key", sort=1, x=1, y=1, m=9), + dict(id="key", sort=2, x=1, y=3, m=9), + dict(id="key", sort=3, x=1, y=2, m=8), + ) def get_pages(self, key, **kwargs): ret = [] @@ -37,28 +38,36 @@ def get_pages(self, key, **kwargs): class TestDynamoAbstraction(unittest.TestCase, Helpers): def setUp(self): - self.table = dynamo.Table(dynamo.MemoryStorage(), 'table', 'id') + self.table = dynamo.Table(dynamo.MemoryStorage(), "table", "id") def test_set_manipulation(self): """Test that adding to a set and removing from a set works.""" - self.table.create(dict( - id='key', - values=set(['a', 'b', 'c']), - )) - - self.table.update(dict(id='key'), dict( - values=dynamo.DynamoAddToStringSet('x', 'y'), - )) - - final = self.table.get(dict(id='key')) - self.assertEqual(final['values'], set(['a', 'b', 'c', 'x', 'y'])) - - self.table.update(dict(id='key'), dict( - values=dynamo.DynamoRemoveFromStringSet('b', 'c'), - )) - - final = self.table.get(dict(id='key')) - self.assertEqual(final['values'], set(['a', 'x', 'y'])) + self.table.create( + dict( + id="key", + values=set(["a", "b", "c"]), + ) + ) + + self.table.update( + dict(id="key"), + dict( + values=dynamo.DynamoAddToStringSet("x", "y"), + ), + ) + + final = self.table.get(dict(id="key")) + self.assertEqual(final["values"], set(["a", "b", "c", "x", "y"])) + + self.table.update( + dict(id="key"), + dict( + values=dynamo.DynamoRemoveFromStringSet("b", "c"), + ), + ) + + final = self.table.get(dict(id="key")) + self.assertEqual(final["values"], set(["a", "x", "y"])) def test_cannot_set_manipulate_lists(self): """Test that if a value originally got created as a 'list', we cannot @@ -67,60 +76,74 @@ def test_cannot_set_manipulate_lists(self): This also doesn't work in Real DynamoDB, our in-memory API shouldn't make this appear to work. """ - self.table.create(dict( - id='key', - values=['a', 'b', 'c'], # List instead of set - )) + self.table.create( + dict( + id="key", + values=["a", "b", "c"], # List instead of set + ) + ) with self.assertRaises(TypeError): - self.table.update(dict(id='key'), dict( - values=dynamo.DynamoAddToStringSet('x', 'y'), - )) + self.table.update( + dict(id="key"), + dict( + values=dynamo.DynamoAddToStringSet("x", "y"), + ), + ) def test_sets_are_serialized_properly(self): """Test that adding to a set and removing from a set works.""" - with with_clean_file('test.json'): - table = dynamo.Table(dynamo.MemoryStorage('test.json'), 'table', 'id') - table.create(dict( - id='key', - values=set(['a', 'b', 'c']), - )) - - table = dynamo.Table(dynamo.MemoryStorage('test.json'), 'table', 'id') - final = table.get(dict(id='key')) - self.assertEqual(final['values'], set(['a', 'b', 'c'])) + with with_clean_file("test.json"): + table = dynamo.Table(dynamo.MemoryStorage("test.json"), "table", "id") + table.create( + dict( + id="key", + values=set(["a", "b", "c"]), + ) + ) + + table = dynamo.Table(dynamo.MemoryStorage("test.json"), "table", "id") + final = table.get(dict(id="key")) + self.assertEqual(final["values"], set(["a", "b", "c"])) def test_batch_get(self): - self.insert( - dict(id='k1', bla=1), - dict(id='k2', bla=2), - dict(id='k3', bla=3)) + self.insert(dict(id="k1", bla=1), dict(id="k2", bla=2), dict(id="k3", bla=3)) # Test list API - result = self.table.batch_get([ - dict(id='k1'), - dict(id='oeps'), - dict(id='k2'), - ]) - - self.assertEqual(result, [ - dict(id='k1', bla=1), - None, - dict(id='k2', bla=2), - ]) + result = self.table.batch_get( + [ + dict(id="k1"), + dict(id="oeps"), + dict(id="k2"), + ] + ) + + self.assertEqual( + result, + [ + dict(id="k1", bla=1), + None, + dict(id="k2", bla=2), + ], + ) # Test dict API - result = self.table.batch_get({ - 'a': dict(id='k1'), - 'b': dict(id='k2'), - 'z': dict(id='oeps'), - }) - - self.assertEqual(result, { - 'a': dict(id='k1', bla=1), - 'b': dict(id='k2', bla=2), - 'z': None, - }) + result = self.table.batch_get( + { + "a": dict(id="k1"), + "b": dict(id="k2"), + "z": dict(id="oeps"), + } + ) + + self.assertEqual( + result, + { + "a": dict(id="k1", bla=1), + "b": dict(id="k2", bla=2), + "z": None, + }, + ) class TestSortKeysInMemory(unittest.TestCase): @@ -128,40 +151,43 @@ class TestSortKeysInMemory(unittest.TestCase): def setUp(self): self.table = dynamo.Table( - dynamo.MemoryStorage(), - 'table', - partition_key='id', - sort_key='sort') + dynamo.MemoryStorage(), "table", partition_key="id", sort_key="sort" + ) def test_put_and_get(self): - self.table.create(dict( - id='key', - sort='sort', - value='V', - )) + self.table.create( + dict( + id="key", + sort="sort", + value="V", + ) + ) - ret = self.table.get(dict(id='key', sort='sort')) - self.assertEqual(ret['value'], 'V') + ret = self.table.get(dict(id="key", sort="sort")) + self.assertEqual(ret["value"], "V") def test_put_and_get_many(self): # Insert in reverse order - self.table.create(dict(id='key', sort='b', value='B')) - self.table.create(dict(id='key', sort='a', value='A')) + self.table.create(dict(id="key", sort="b", value="B")) + self.table.create(dict(id="key", sort="a", value="A")) # Get them back in the right order - ret = list(self.table.get_many(dict(id='key'))) - self.assertEqual(ret, [ - dict(id='key', sort='a', value='A'), - dict(id='key', sort='b', value='B'), - ]) + ret = list(self.table.get_many(dict(id="key"))) + self.assertEqual( + ret, + [ + dict(id="key", sort="a", value="A"), + dict(id="key", sort="b", value="B"), + ], + ) def test_updates(self): # Two updates to a record with a sort key - self.table.update(dict(id='key', sort='s'), dict(x='x')) - self.table.update(dict(id='key', sort='s'), dict(y='y')) + self.table.update(dict(id="key", sort="s"), dict(x="x")) + self.table.update(dict(id="key", sort="s"), dict(y="y")) - ret = self.table.get(dict(id='key', sort='s')) - self.assertEqual(ret, dict(id='key', sort='s', x='x', y='y')) + ret = self.table.get(dict(id="key", sort="s")) + self.assertEqual(ret, dict(id="key", sort="s", x="x", y="y")) class TestQueryInMemory(unittest.TestCase, Helpers): @@ -170,202 +196,245 @@ class TestQueryInMemory(unittest.TestCase, Helpers): def setUp(self): self.table = dynamo.Table( dynamo.MemoryStorage(), - 'table', - partition_key='id', - sort_key='sort', + "table", + partition_key="id", + sort_key="sort", indexes=[ - dynamo.Index( - 'x', - 'y'), - dynamo.Index('m'), - dynamo.Index('n', keys_only=True), - ]) + dynamo.Index("x", "y"), + dynamo.Index("m"), + dynamo.Index("n", keys_only=True), + ], + ) def test_query(self): - self.table.create({'id': 'key', 'sort': 1, 'm': 'val'}) - self.table.create({'id': 'key', 'sort': 2, 'm': 'another'}) + self.table.create({"id": "key", "sort": 1, "m": "val"}) + self.table.create({"id": "key", "sort": 2, "m": "another"}) - ret = list(self.table.get_many({'id': 'key'})) + ret = list(self.table.get_many({"id": "key"})) - self.assertEqual(ret, [ - {'id': 'key', 'sort': 1, 'm': 'val'}, - {'id': 'key', 'sort': 2, 'm': 'another'} - ]) + self.assertEqual( + ret, + [ + {"id": "key", "sort": 1, "m": "val"}, + {"id": "key", "sort": 2, "m": "another"}, + ], + ) def test_between_query(self): - self.table.create({'id': 'key', 'sort': 1, 'x': 'x'}) - self.table.create({'id': 'key', 'sort': 2, 'x': 'y'}) - self.table.create({'id': 'key', 'sort': 3, 'x': 'z'}) - - ret = list(self.table.get_many({ - 'id': 'key', - 'sort': dynamo.Between(2, 5), - })) - self.assertEqual(ret, [ - {'id': 'key', 'sort': 2, 'x': 'y'}, - {'id': 'key', 'sort': 3, 'x': 'z'}, - ]) + self.table.create({"id": "key", "sort": 1, "x": "x"}) + self.table.create({"id": "key", "sort": 2, "x": "y"}) + self.table.create({"id": "key", "sort": 3, "x": "z"}) + + ret = list( + self.table.get_many( + { + "id": "key", + "sort": dynamo.Between(2, 5), + } + ) + ) + self.assertEqual( + ret, + [ + {"id": "key", "sort": 2, "x": "y"}, + {"id": "key", "sort": 3, "x": "z"}, + ], + ) def test_query_index(self): - self.table.create({'id': 'key', 'sort': 1, 'm': 'val'}) - self.table.create({'id': 'key', 'sort': 2, 'm': 'another'}) + self.table.create({"id": "key", "sort": 1, "m": "val"}) + self.table.create({"id": "key", "sort": 2, "m": "another"}) - ret = list(self.table.get_many({'m': 'val'})) + ret = list(self.table.get_many({"m": "val"})) - self.assertEqual(ret, [ - {'id': 'key', 'sort': 1, 'm': 'val'} - ]) + self.assertEqual(ret, [{"id": "key", "sort": 1, "m": "val"}]) def test_query_index_with_partition_key(self): - self.table.create({'id': 'key', 'sort': 1, 'x': 'val', 'y': 0}) - self.table.create({'id': 'key', 'sort': 2, 'x': 'val', 'y': 1}) - self.table.create({'id': 'key', 'sort': 3, 'x': 'val', 'y': 1}) - self.table.create({'id': 'key', 'sort': 4, 'x': 'another_val', 'y': 2}) - - ret = list(self.table.get_many({'x': 'val'})) - - self.assertEqual(ret, [ - {'id': 'key', 'sort': 1, 'x': 'val', 'y': 0}, - {'id': 'key', 'sort': 2, 'x': 'val', 'y': 1}, - {'id': 'key', 'sort': 3, 'x': 'val', 'y': 1} - ]) + self.table.create({"id": "key", "sort": 1, "x": "val", "y": 0}) + self.table.create({"id": "key", "sort": 2, "x": "val", "y": 1}) + self.table.create({"id": "key", "sort": 3, "x": "val", "y": 1}) + self.table.create({"id": "key", "sort": 4, "x": "another_val", "y": 2}) + + ret = list(self.table.get_many({"x": "val"})) + + self.assertEqual( + ret, + [ + {"id": "key", "sort": 1, "x": "val", "y": 0}, + {"id": "key", "sort": 2, "x": "val", "y": 1}, + {"id": "key", "sort": 3, "x": "val", "y": 1}, + ], + ) def test_query_index_with_partition_sort_key(self): - self.table.create({'id': 'key', 'sort': 1, 'x': 'val', 'y': 0}) - self.table.create({'id': 'key', 'sort': 2, 'x': 'val', 'y': 1}) - self.table.create({'id': 'key', 'sort': 3, 'x': 'val', 'y': 1}) - self.table.create({'id': 'key', 'sort': 4, 'x': 'another_val', 'y': 2}) + self.table.create({"id": "key", "sort": 1, "x": "val", "y": 0}) + self.table.create({"id": "key", "sort": 2, "x": "val", "y": 1}) + self.table.create({"id": "key", "sort": 3, "x": "val", "y": 1}) + self.table.create({"id": "key", "sort": 4, "x": "another_val", "y": 2}) - ret = list(self.table.get_many({'x': 'val', 'y': 1})) + ret = list(self.table.get_many({"x": "val", "y": 1})) - self.assertEqual(ret, [ - {'id': 'key', 'sort': 2, 'x': 'val', 'y': 1}, - {'id': 'key', 'sort': 3, 'x': 'val', 'y': 1} - ]) + self.assertEqual( + ret, + [ + {"id": "key", "sort": 2, "x": "val", "y": 1}, + {"id": "key", "sort": 3, "x": "val", "y": 1}, + ], + ) def test_query_index_sort_key_between(self): - self.table.create({'id': 'key', 'sort': 1, 'x': 'val', 'y': 1}) - self.table.create({'id': 'key', 'sort': 2, 'x': 'val', 'y': 3}) - self.table.create({'id': 'key', 'sort': 3, 'x': 'val', 'y': 6}) - - ret = list(self.table.get_many({ - 'x': 'val', - 'y': dynamo.Between(2, 5), - })) - self.assertEqual(ret, [ - {'id': 'key', 'sort': 2, 'x': 'val', 'y': 3} - ]) + self.table.create({"id": "key", "sort": 1, "x": "val", "y": 1}) + self.table.create({"id": "key", "sort": 2, "x": "val", "y": 3}) + self.table.create({"id": "key", "sort": 3, "x": "val", "y": 6}) + + ret = list( + self.table.get_many( + { + "x": "val", + "y": dynamo.Between(2, 5), + } + ) + ) + self.assertEqual(ret, [{"id": "key", "sort": 2, "x": "val", "y": 3}]) def test_paginated_query(self): self.insert_sample_data() - pages = self.get_pages({'id': 'key'}, limit=1) + pages = self.get_pages({"id": "key"}, limit=1) - self.assertEqual(pages, [ - [dict(id='key', sort=1, x=1, y=1, m=9)], - [dict(id='key', sort=2, x=1, y=3, m=9)], - [dict(id='key', sort=3, x=1, y=2, m=8)], - ]) + self.assertEqual( + pages, + [ + [dict(id="key", sort=1, x=1, y=1, m=9)], + [dict(id="key", sort=2, x=1, y=3, m=9)], + [dict(id="key", sort=3, x=1, y=2, m=8)], + ], + ) def test_paginated_query_reverse(self): self.insert_sample_data() - pages = self.get_pages({'id': 'key'}, limit=1, reverse=True) + pages = self.get_pages({"id": "key"}, limit=1, reverse=True) - self.assertEqual(pages, [ - [dict(id='key', sort=3, x=1, y=2, m=8)], - [dict(id='key', sort=2, x=1, y=3, m=9)], - [dict(id='key', sort=1, x=1, y=1, m=9)], - ]) + self.assertEqual( + pages, + [ + [dict(id="key", sort=3, x=1, y=2, m=8)], + [dict(id="key", sort=2, x=1, y=3, m=9)], + [dict(id="key", sort=1, x=1, y=1, m=9)], + ], + ) def test_paginated_query_on_sortkey_index(self): self.insert_sample_data() - pages = self.get_pages({'x': 1}, limit=1) + pages = self.get_pages({"x": 1}, limit=1) - self.assertEqual(pages, [ - [dict(id='key', sort=1, x=1, y=1, m=9)], - [dict(id='key', sort=3, x=1, y=2, m=8)], - [dict(id='key', sort=2, x=1, y=3, m=9)], - ]) + self.assertEqual( + pages, + [ + [dict(id="key", sort=1, x=1, y=1, m=9)], + [dict(id="key", sort=3, x=1, y=2, m=8)], + [dict(id="key", sort=2, x=1, y=3, m=9)], + ], + ) def test_paginated_query_on_sortkey_index_reverse(self): self.insert_sample_data() - pages = self.get_pages({'x': 1}, limit=1, reverse=True) + pages = self.get_pages({"x": 1}, limit=1, reverse=True) - self.assertEqual(pages, [ - [dict(id='key', sort=2, x=1, y=3, m=9)], - [dict(id='key', sort=3, x=1, y=2, m=8)], - [dict(id='key', sort=1, x=1, y=1, m=9)], - ]) + self.assertEqual( + pages, + [ + [dict(id="key", sort=2, x=1, y=3, m=9)], + [dict(id="key", sort=3, x=1, y=2, m=8)], + [dict(id="key", sort=1, x=1, y=1, m=9)], + ], + ) def test_paginated_query_on_partitionkey_index(self): self.insert_sample_data() - pages = self.get_pages({'m': 9}, limit=1) + pages = self.get_pages({"m": 9}, limit=1) - self.assertEqual(pages, [ - [dict(id='key', sort=1, x=1, y=1, m=9)], - [dict(id='key', sort=2, x=1, y=3, m=9)], - ]) + self.assertEqual( + pages, + [ + [dict(id="key", sort=1, x=1, y=1, m=9)], + [dict(id="key", sort=2, x=1, y=3, m=9)], + ], + ) def test_paginated_query_on_partitionkey_index_reverse(self): self.insert_sample_data() - pages = self.get_pages({'m': 9}, limit=1, reverse=True) + pages = self.get_pages({"m": 9}, limit=1, reverse=True) - self.assertEqual(pages, [ - [dict(id='key', sort=2, x=1, y=3, m=9)], - [dict(id='key', sort=1, x=1, y=1, m=9)], - ]) + self.assertEqual( + pages, + [ + [dict(id="key", sort=2, x=1, y=3, m=9)], + [dict(id="key", sort=1, x=1, y=1, m=9)], + ], + ) def test_paginated_scan(self): self.insert( - dict(id='key', sort=1, y=1), - dict(id='key', sort=2, y=3), - dict(id='key', sort=3, y=6)) + dict(id="key", sort=1, y=1), + dict(id="key", sort=2, y=3), + dict(id="key", sort=3, y=6), + ) ret = self.table.scan(limit=1) - self.assertEqual(ret[0], {'id': 'key', 'sort': 1, 'y': 1}) + self.assertEqual(ret[0], {"id": "key", "sort": 1, "y": 1}) ret = self.table.scan(limit=1, pagination_token=ret.next_page_token) - self.assertEqual(ret[0], {'id': 'key', 'sort': 2, 'y': 3}) + self.assertEqual(ret[0], {"id": "key", "sort": 2, "y": 3}) ret = self.table.scan(limit=1, pagination_token=ret.next_page_token) - self.assertEqual(ret[0], {'id': 'key', 'sort': 3, 'y': 6}) + self.assertEqual(ret[0], {"id": "key", "sort": 3, "y": 6}) self.assertIsNone(ret.next_page_token) def test_keys_only_index(self): self.insert( - dict(id='key', sort=1, n=1, other='1'), - dict(id='key', sort=2, n=1, other='2'), - dict(id='key', sort=3, n=1, other='3')) + dict(id="key", sort=1, n=1, other="1"), + dict(id="key", sort=2, n=1, other="2"), + dict(id="key", sort=3, n=1, other="3"), + ) - ret = self.table.get_many({'n': 1}) + ret = self.table.get_many({"n": 1}) # This is a keys_only index, so we expect to get { id, sort, n } back - self.assertEqual(ret.records, [ - dict(id='key', sort=1, n=1), - dict(id='key', sort=2, n=1), - dict(id='key', sort=3, n=1), - ]) + self.assertEqual( + ret.records, + [ + dict(id="key", sort=1, n=1), + dict(id="key", sort=2, n=1), + dict(id="key", sort=3, n=1), + ], + ) def test_get_many_iterator(self): N = 10 for i in range(N): - self.insert( - dict(id='key', sort=i + 1)) + self.insert(dict(id="key", sort=i + 1)) - expected = [dict(id='key', sort=i + 1) for i in range(N)] + expected = [dict(id="key", sort=i + 1) for i in range(N)] # Query everything at once, using the Python iterator protocol - self.assertEqual(list(dynamo.GetManyIterator(self.table, dict(id='key'))), expected) + self.assertEqual( + list(dynamo.GetManyIterator(self.table, dict(id="key"))), expected + ) # Query paginated, using the Python iterator protocol - self.assertEqual(list(dynamo.GetManyIterator(self.table, dict(id='key'), limit=3)), expected) + self.assertEqual( + list(dynamo.GetManyIterator(self.table, dict(id="key"), limit=3)), expected + ) # Reuse the client-side pager every time, using the Python iterator protocol ret = [] token = None while True: - many = dynamo.GetManyIterator(self.table, dict(id='key'), limit=3, pagination_token=token) + many = dynamo.GetManyIterator( + self.table, dict(id="key"), limit=3, pagination_token=token + ) ret.append(next(iter(many))) token = many.next_page_token if not token: @@ -374,7 +443,9 @@ def test_get_many_iterator(self): # Also test using the eof/current/advance protocol ret = [] - many = dynamo.GetManyIterator(self.table, dict(id='key'), limit=3, pagination_token=token) + many = dynamo.GetManyIterator( + self.table, dict(id="key"), limit=3, pagination_token=token + ) while many: ret.append(many.current) many.advance() @@ -387,27 +458,32 @@ class TestSortKeysAgainstAws(unittest.TestCase): def setUp(self): self.db = mock.Mock() self.table = dynamo.Table( - dynamo.AwsDynamoStorage( - self.db, - ''), - 'table', - partition_key='id', - sort_key='sort') + dynamo.AwsDynamoStorage(self.db, ""), + "table", + partition_key="id", + sort_key="sort", + ) - self.db.query.return_value = {'Items': []} + self.db.query.return_value = {"Items": []} def test_between_query(self): - self.table.get_many({ - 'id': 'key', - 'sort': dynamo.Between(2, 5), - }) + self.table.get_many( + { + "id": "key", + "sort": dynamo.Between(2, 5), + } + ) self.db.query.assert_called_with( - KeyConditionExpression='#id = :id AND #sort BETWEEN :sort_min AND :sort_max', ExpressionAttributeValues={ - ':id': { - 'S': 'key'}, ':sort_min': { - 'N': '2'}, ':sort_max': { - 'N': '5'}}, ExpressionAttributeNames={ - '#id': 'id', '#sort': 'sort'}, TableName=mock.ANY, ScanIndexForward=mock.ANY) + KeyConditionExpression="#id = :id AND #sort BETWEEN :sort_min AND :sort_max", + ExpressionAttributeValues={ + ":id": {"S": "key"}, + ":sort_min": {"N": "2"}, + ":sort_max": {"N": "5"}, + }, + ExpressionAttributeNames={"#id": "id", "#sort": "sort"}, + TableName=mock.ANY, + ScanIndexForward=mock.ANY, + ) def try_to_delete(filename): diff --git a/tests/test_highlighting/test_WEBSITE.py b/tests/test_highlighting/test_WEBSITE.py index 60e8abcc680..22409acf305 100644 --- a/tests/test_highlighting/test_WEBSITE.py +++ b/tests/test_highlighting/test_WEBSITE.py @@ -2,12 +2,10 @@ class HighlighterTestWebSite(HighlightTester): - def test_1_1(self): self.assert_highlighted_chr( - "print hello world!", - "KKKKK TTTTTTTTTTTT", - level="level1", lang='en') + "print hello world!", "KKKKK TTTTTTTTTTTT", level="level1", lang="en" + ) def test_1_2(self): self.assert_highlighted_chr_multi_line( @@ -15,7 +13,9 @@ def test_1_2(self): "KKKKK TTTTTT", "print Welcome to Hedy!", "KKKKK TTTTTTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_3(self): self.assert_highlighted_chr_multi_line( @@ -23,19 +23,25 @@ def test_1_3(self): "KKK TTTTTTTTTTTTTTTTTT", "echo hello", "KKKK TTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_4(self): self.assert_highlighted_chr( "print Your story starts here", "KKKKK TTTTTTTTTTTTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_5(self): self.assert_highlighted_chr( "ask who is the star in your story?", "KKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_6(self): self.assert_highlighted_chr_multi_line( @@ -49,13 +55,17 @@ def test_1_6(self): "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", "print He's afraid this is a haunted forest", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_7(self): self.assert_highlighted_chr( "print Im Hedy the parrot", "KKKKK TTTTTTTTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_8(self): self.assert_highlighted_chr_multi_line( @@ -67,7 +77,9 @@ def test_1_8(self): "KKKK", "echo", "KKKK", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_9(self): self.assert_highlighted_chr_multi_line( @@ -75,7 +87,9 @@ def test_1_9(self): "KKKKKKK TT", "turn left", "KKKK TTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_10(self): self.assert_highlighted_chr_multi_line( @@ -87,13 +101,17 @@ def test_1_10(self): "KKKK TTTT", "forward 50", "KKKKKKK TT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_11(self): self.assert_highlighted_chr( "print Welcome to your own rock scissors paper!", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_12(self): self.assert_highlighted_chr_multi_line( @@ -103,13 +121,17 @@ def test_1_12(self): "KKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", "echo so your choice was:", "KKKK TTTTTTTTTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_13(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_14(self): self.assert_highlighted_chr_multi_line( @@ -123,7 +145,9 @@ def test_1_14(self): "KKKKK TTTTTTTTTTTTTTTTT", "echo Your name is", "KKKK TTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_15(self): self.assert_highlighted_chr_multi_line( @@ -137,13 +161,17 @@ def test_1_15(self): "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTT", "print It's on its way!", "KKKKK TTTTTTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_16(self): self.assert_highlighted_chr( "print How did I get here?", "KKKKK TTTTTTTTTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_17(self): self.assert_highlighted_chr_multi_line( @@ -171,13 +199,14 @@ def test_1_17(self): "KKKK TTTTTTTTTTTTT", "print ...?", "KKKKK TTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_1_18(self): self.assert_highlighted_chr( - "print Let's go!", - "KKKKK TTTTTTTTT", - level="level1", lang='en') + "print Let's go!", "KKKKK TTTTTTTTT", level="level1", lang="en" + ) def test_1_19(self): self.assert_highlighted_chr_multi_line( @@ -191,13 +220,14 @@ def test_1_19(self): "KKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTT", "echo So you want ", "KKKK TTTTTTTTTTTT", - level="level1", lang='en') + level="level1", + lang="en", + ) def test_2_1(self): self.assert_highlighted_chr( - "print hello world!", - "KKKKK TTTTTTTTTTTT", - level="level2", lang='en') + "print hello world!", "KKKKK TTTTTTTTTTTT", level="level2", lang="en" + ) def test_2_2(self): self.assert_highlighted_chr_multi_line( @@ -207,7 +237,9 @@ def test_2_2(self): "TTT KK TT", "print name is age years old", "KKKKK TTTTTTTTTTTTTTTTTTTTT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_3(self): self.assert_highlighted_chr_multi_line( @@ -215,7 +247,9 @@ def test_2_3(self): "TTTTTT KK KKK TTTTTTTTTTTTTTTTTT", "print Hello answer", "KKKKK TTTTTTTTTTTT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_4(self): self.assert_highlighted_chr_multi_line( @@ -225,13 +259,14 @@ def test_2_4(self): "KKKKK T", "print green!", "KKKKK TTTTTT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_5(self): self.assert_highlighted_chr( - "print Your story", - "KKKKK TTTTTTTTTT", - level="level2", lang='en') + "print Your story", "KKKKK TTTTTTTTTT", level="level2", lang="en" + ) def test_2_6(self): self.assert_highlighted_chr_multi_line( @@ -247,13 +282,17 @@ def test_2_6(self): "KKKKK", "print name is afraid this is a haunted forest", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_7(self): self.assert_highlighted_chr( "print Im Hedy the parrot!", "KKKKK TTTTTTTTTTTTTTTTTTT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_8(self): self.assert_highlighted_chr_multi_line( @@ -271,7 +310,9 @@ def test_2_8(self): "KKKKK", "print name", "KKKKK TTTT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_9(self): self.assert_highlighted_chr_multi_line( @@ -283,7 +324,9 @@ def test_2_9(self): "KKKK TTTT", "forward 25", "KKKKKKK TT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_10(self): self.assert_highlighted_chr_multi_line( @@ -299,13 +342,17 @@ def test_2_10(self): "KKKK TTTTT", "forward 25", "KKKKKKK TT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_11(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_12(self): self.assert_highlighted_chr_multi_line( @@ -313,7 +360,9 @@ def test_2_12(self): "TTTTTT KK I", "print I choose choice", "KKKKK TTTTTTTTTTTTTTT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_13(self): self.assert_highlighted_chr_multi_line( @@ -335,13 +384,14 @@ def test_2_13(self): "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTT", "print Your food and drinks will be right there!", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_14(self): self.assert_highlighted_chr( - "monster1 is _", - "TTTTTTTT KK I", - level="level2", lang='en') + "monster1 is _", "TTTTTTTT KK I", level="level2", lang="en" + ) def test_2_15(self): self.assert_highlighted_chr_multi_line( @@ -361,19 +411,22 @@ def test_2_15(self): "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", "print But as you enter monster_3 attacks you!", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_2_16(self): self.assert_highlighted_chr( "print Let's go to the next level!", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level2", lang='en') + level="level2", + lang="en", + ) def test_3_1(self): self.assert_highlighted_chr_multi_line( - "print hello world!", - "KKKKK TTTTTTTTTTTT", - level="level3", lang='en') + "print hello world!", "KKKKK TTTTTTTTTTTT", level="level3", lang="en" + ) def test_3_2(self): self.assert_highlighted_chr_multi_line( @@ -381,7 +434,9 @@ def test_3_2(self): "TTTTTTT KK TTTK TTTK TTTTTTTT", "print animals at random", "KKKKK TTTTTTT KK KKKKKK", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_3(self): self.assert_highlighted_chr_multi_line( @@ -393,13 +448,14 @@ def test_3_3(self): "KKKKKK TTT KKKK TTTTTTT", "print animals at random", "KKKKK TTTTTTT KK KKKKKK", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_4(self): self.assert_highlighted_chr_multi_line( - "print Your story", - "KKKKK TTTTTTTTTT", - level="level3", lang='en') + "print Your story", "KKKKK TTTTTTTTTT", level="level3", lang="en" + ) def test_3_5(self): self.assert_highlighted_chr_multi_line( @@ -407,7 +463,9 @@ def test_3_5(self): "TTTTTTT KK TK TK TK T", "print He now hears the sound of an animals at random", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT KK KKKKKK", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_6(self): self.assert_highlighted_chr_multi_line( @@ -421,7 +479,9 @@ def test_3_6(self): "KKK TTTTTT KK TTTTTTT", "print it was a animals at random", "KKKKK TTTTTTTTTTTTTTTT KK KKKKKK", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_7(self): self.assert_highlighted_chr_multi_line( @@ -435,7 +495,9 @@ def test_3_7(self): "TTTT KK KKK TTTTTTTTTTTTTTTTTTTTTTTTTT", "remove dump from bag", "KKKKKK TTTT KKKK TTT", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_8(self): self.assert_highlighted_chr_multi_line( @@ -451,13 +513,17 @@ def test_3_8(self): "KKKKK TTTTTTTTTTTTTTTTTTTTTT", "print 🦜 words at random", "KKKKK TTTTTTT KK KKKKKK", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_9(self): self.assert_highlighted_chr_multi_line( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_10(self): self.assert_highlighted_chr_multi_line( @@ -467,13 +533,17 @@ def test_3_10(self): "KKKK TTTTTT KK KKKKKK", "forward 25", "KKKKKKK TT", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_11(self): self.assert_highlighted_chr_multi_line( "print Who does the dishes?", "KKKKK TTTTTTTTTTTTTTTTTTTT", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_12(self): self.assert_highlighted_chr_multi_line( @@ -481,7 +551,9 @@ def test_3_12(self): "TTTTTT KK TTTK TTTK TTTTK TTTTTT", "print people at random", "KKKKK TTTTTT KK KKKKKK", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_13(self): self.assert_highlighted_chr_multi_line( @@ -493,13 +565,17 @@ def test_3_13(self): "KKKKKK TTTTTTTTT KKKK TTTTTT", "print people at random does the dishes", "KKKKK TTTTTT KK KKKKKK TTTTTTTTTTTTTTT", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_14(self): self.assert_highlighted_chr_multi_line( "print What will the die indicate this time?", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_15(self): self.assert_highlighted_chr_multi_line( @@ -507,13 +583,17 @@ def test_3_15(self): "TTTTTTT KK TK TK TK TK TK TTTTTTTTT", "print choices at random", "KKKKK TTTTTTT KK KKKKKK", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_16(self): self.assert_highlighted_chr_multi_line( "print Welcome to your own rock scissors paper!", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_17(self): self.assert_highlighted_chr_multi_line( @@ -521,7 +601,9 @@ def test_3_17(self): "TTTTTTT KK TTTTK TTTTTK TTTTTTTT", "print choices at random", "KKKKK TTTTTTT KK KKKKKK", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_18(self): self.assert_highlighted_chr_multi_line( @@ -539,7 +621,9 @@ def test_3_18(self): "KKKKK T", "print answers at random", "KKKKK TTTTTTT KK KKKKKK", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_19(self): self.assert_highlighted_chr_multi_line( @@ -553,7 +637,9 @@ def test_3_19(self): "KKKKKK TTTTTTTTT KKKK TTTTTTT", "print You get a flavors at random milkshake", "KKKKK TTTTTTTTTTTTTTTTT KK KKKKKK TTTTTTTTT", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_20(self): self.assert_highlighted_chr_multi_line( @@ -583,7 +669,9 @@ def test_3_20(self): "KKKKK TTTTTTTTTTTTTTTTTTTT KK KKKKKK", "print Thank you and enjoy your meal!", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_21(self): self.assert_highlighted_chr_multi_line( @@ -603,7 +691,9 @@ def test_3_21(self): "KKKKK", "print monsters at random", "KKKKK TTTTTTTT KK KKKKKK", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_22(self): self.assert_highlighted_chr_multi_line( @@ -611,19 +701,22 @@ def test_3_22(self): "TTTT KK TTTTTT", "print My name is name", "KKKKK TTTTTTTTTTTTTTT", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_3_23(self): self.assert_highlighted_chr( "print Let's go to the next level!", "KKKKK TTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level3", lang='en') + level="level3", + lang="en", + ) def test_4_1(self): self.assert_highlighted_chr_multi_line( - "print 'Hello world'", - "KKKKK SSSSSSSSSSSSS", - level="level4", lang='en') + "print 'Hello world'", "KKKKK SSSSSSSSSSSSS", level="level4", lang="en" + ) def test_4_2(self): self.assert_highlighted_chr_multi_line( @@ -633,13 +726,17 @@ def test_4_2(self): "TTTTTT KK KKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", "print 'We need to use ' answer", "KKKKK SSSSSSSSSSSSSSSSS TTTTTT", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_3(self): self.assert_highlighted_chr_multi_line( "print 'Your story will be printed here!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_4(self): self.assert_highlighted_chr_multi_line( @@ -657,7 +754,9 @@ def test_4_4(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSS TTTTTTT KK KKKKKK", "print name 'is afraid this is a haunted forest'", "KKKKK TTTT SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_5(self): self.assert_highlighted_chr_multi_line( @@ -669,7 +768,9 @@ def test_4_5(self): "KKKK TTTTT", "forward 25", "KKKKKKK TT", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_6(self): self.assert_highlighted_chr_multi_line( @@ -685,13 +786,17 @@ def test_4_6(self): "KKKK TTTTT", "forward 25", "KKKKKKK TT", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_7(self): self.assert_highlighted_chr_multi_line( "print 'Who does the dishes?'", "KKKKK SSSSSSSSSSSSSSSSSSSSSS", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_8(self): self.assert_highlighted_chr_multi_line( @@ -703,7 +808,9 @@ def test_4_8(self): "KKKKK", "print people at _", "KKKKK TTTTTT KK I", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_9(self): self.assert_highlighted_chr_multi_line( @@ -715,13 +822,17 @@ def test_4_9(self): "KKKKK", "print people at random", "KKKKK TTTTTT KK KKKKKK", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_10(self): self.assert_highlighted_chr_multi_line( "print 'What will the die indicate this time?'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_11(self): self.assert_highlighted_chr_multi_line( @@ -731,7 +842,9 @@ def test_4_11(self): "KKKKK I TTTTTTTTT I", "print _ _ _ # here you have to program the choice", "KKKKK I I I CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_12(self): self.assert_highlighted_chr_multi_line( @@ -741,13 +854,17 @@ def test_4_12(self): "KKKKK SSSSSSSSSSSSS", "print choices at random # here you have to program the choice", "KKKKK TTTTTTT KK KKKKKK CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_13(self): self.assert_highlighted_chr_multi_line( "print 'Welcome to your own rock scissors paper!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_14(self): self.assert_highlighted_chr_multi_line( @@ -755,7 +872,9 @@ def test_4_14(self): "TTTTTTT KK TTTTK TTTTTK TTTTTTTT", "print _ The computer chose: _ _ at _", "KKKKK I TTTTTTTTTTTTTTTTTTT I I KK I", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_15(self): self.assert_highlighted_chr_multi_line( @@ -763,13 +882,17 @@ def test_4_15(self): "TTTTTTT KK TTTTK TTTTTK TTTTTTTT", "print ' The computer chose: ' choices at random", "KKKKK SSSSSSSSSSSSSSSSSSSSSSS TTTTTTT KK KKKKKK", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_16(self): self.assert_highlighted_chr_multi_line( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_17(self): self.assert_highlighted_chr_multi_line( @@ -787,7 +910,9 @@ def test_4_17(self): "KKKKK T", "print answers at random", "KKKKK TTTTTTT KK KKKKKK", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_18(self): self.assert_highlighted_chr_multi_line( @@ -809,7 +934,9 @@ def test_4_18(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSS", "print 'Your ' food ' and ' drinks ' will be right there!'", "KKKKK SSSSSSS TTTT SSSSSSS TTTTTT SSSSSSSSSSSSSSSSSSSSSSS", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_19(self): self.assert_highlighted_chr_multi_line( @@ -829,19 +956,25 @@ def test_4_19(self): "KKKKK", "print monsters at random", "KKKKK TTTTTTTT KK KKKKKK", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_20(self): self.assert_highlighted_chr_multi_line( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_21(self): self.assert_highlighted_chr_multi_line( "password is ask 'What is the correct password?'", "TTTTTTTT KK KKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_4_22(self): self.assert_highlighted_chr_multi_line( @@ -849,7 +982,9 @@ def test_4_22(self): "KKKKK TTTTTTTTTTTTTTTTTTTTTTTT", 'print "Did i forget something?', "KKKKK TTTTTTTTTTTTTTTTTTTTTTTT", - level="level4", lang='en') + level="level4", + lang="en", + ) def test_5_1(self): self.assert_highlighted_chr_multi_line( @@ -857,7 +992,9 @@ def test_5_1(self): "TTTT KK KKK SSSSSSSSSSSSSSSSSSSS", "if name is Hedy print 'cool!' else print 'meh'", "KK TTTT KK TTTT KKKKK SSSSSSS KKKK KKKKK SSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_2(self): self.assert_highlighted_chr_multi_line( @@ -865,7 +1002,9 @@ def test_5_2(self): "TTTT KK KKK SSSSSSSSSSSSSSSSSSSS", "if name is Hedy print 'nice' else print 'boo!'", "KK TTTT KK TTTT KKKKK SSSSSS KKKK KKKKK SSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_3(self): self.assert_highlighted_chr_multi_line( @@ -877,7 +1016,9 @@ def test_5_3(self): "KK TTTTTTTTTTTTTT KK TTTTTTTTTTTTT KKKKK SSSSSSSSS", "else print 'meh'", "KKKK KKKKK SSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_4(self): self.assert_highlighted_chr_multi_line( @@ -887,7 +1028,9 @@ def test_5_4(self): "KK TTTT KK TTTT KKKKK SSSSSS", "else print 'boo!'", "KKKK KKKKK SSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_5(self): self.assert_highlighted_chr_multi_line( @@ -903,13 +1046,17 @@ def test_5_5(self): "KK TTT KK TTTT KKKKK TTTT SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", "else print 'The monster eats' name", "KKKK KKKKK SSSSSSSSSSSSSSSSSS TTTT", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_6(self): self.assert_highlighted_chr( "print 'Here your story will start!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_7(self): self.assert_highlighted_chr_multi_line( @@ -931,13 +1078,17 @@ def test_5_7(self): "KK TTTTTTTTT KK TTTTTTTT KKKKK SSSSSSSSSSSSSSSSSSSSSS", "else print '🧒 No, Hedy! Say ' new_word", "KKKK KKKKK SSSSSSSSSSSSSSSSSS TTTTTTTT", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_8(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_9(self): self.assert_highlighted_chr_multi_line( @@ -965,7 +1116,9 @@ def test_5_9(self): "KKKK TTTTT", "forward 25", "KKKKKKK TT", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_10(self): self.assert_highlighted_chr_multi_line( @@ -979,13 +1132,17 @@ def test_5_10(self): "KKKK TTTTT", "forward 25", "KKKKKKK TT", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_12(self): self.assert_highlighted_chr( "print 'Who does the dishes?'", "KKKKK SSSSSSSSSSSSSSSSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_13(self): self.assert_highlighted_chr_multi_line( @@ -997,13 +1154,17 @@ def test_5_13(self): "KKKKK SSSSSSSSSS I SSSSSSSS", "if _ is earthworm print 'You can stop throwing.' _ print 'You have to hear it again!'", "KK I KK TTTTTTTTT KKKKK SSSSSSSSSSSSSSSSSSSSSSSS I KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_14(self): self.assert_highlighted_chr( "print 'What will the die indicate this time?'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_15(self): self.assert_highlighted_chr_multi_line( @@ -1019,13 +1180,17 @@ def test_5_15(self): "KKKKK SSSSSSSSSSSSSSSSS I", "if _ is _ print 'tie!' else print 'no tie'", "KK I KK I KKKKK SSSSSS KKKK KKKKK SSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_16(self): self.assert_highlighted_chr( "print 'Welcome to your own rock scissors paper!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_17(self): self.assert_highlighted_chr_multi_line( @@ -1055,7 +1220,9 @@ def test_5_17(self): "KK TTTTTTTT KK TT KKKKK SSSSSSSSSSS KKKK KKKKK SSSSSS TTTTTTTT", "print 'Thank you for your order and enjoy your meal!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_18(self): self.assert_highlighted_chr_multi_line( @@ -1067,7 +1234,9 @@ def test_5_18(self): "TTTTTT KK KKK SSSSSSSSSSSSSS", "if person is Hedy print 'You will definitely win!🤩' else print 'Bad luck! Someone else will win!😭'", "KK TTTTTT KK TTTT KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSS KKKK KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_19(self): self.assert_highlighted_chr_multi_line( @@ -1083,7 +1252,9 @@ def test_5_19(self): "TTTTTTTTT KK TTTTTTTTTTTTTTTTTTTTK TTTTTTTTTTTTTTTTTTTTTTTK TTTTTTTTT", "if person is Hedy print goodanswer at random else print badanswer at random", "KK TTTTTT KK TTTT KKKKK TTTTTTTTTT KK KKKKKK KKKK KKKKK TTTTTTTTT KK KKKKKK", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_20(self): self.assert_highlighted_chr_multi_line( @@ -1107,7 +1278,9 @@ def test_5_20(self): "KK TTTTTTTTTTT KK TTTTTTTTTTTT KKKKK SSSSSSSSSSSSSSSSSSSSSSS", "else print 'Oh no! You are being eaten by a...' monsters at random", "KKKK KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS TTTTTTTT KK KKKKKK", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_21(self): self.assert_highlighted_chr_multi_line( @@ -1125,7 +1298,9 @@ def test_5_21(self): "KK TTTT KK TTTTTTTTTT KKKKK SSSSSSSS", "else print 'No, frog is grenouille'", "KKKK KKKKK SSSSSSSSSSSSSSSSSSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_22(self): self.assert_highlighted_chr_multi_line( @@ -1147,13 +1322,17 @@ def test_5_22(self): "KKKKK SSSSSSSSSSSSSSS TTTTT SSSSSSSSSSSSSSSSSSSS TTTTT SSSSSSSSS", "print 'The drinks are free in this level because Hedy cant calculate the price yet...'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_23(self): self.assert_highlighted_chr( "print 'On to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSS", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_5_24(self): self.assert_highlighted_chr_multi_line( @@ -1161,13 +1340,17 @@ def test_5_24(self): "KKKKK TTTTTTTTTTTTTTTTTTTTTTTT", 'print "Did i forget something?', "KKKKK TTTTTTTTTTTTTTTTTTTTTTTT", - level="level5", lang='en') + level="level5", + lang="en", + ) def test_6_1(self): self.assert_highlighted_chr( "print '5 times 5 is ' 5 * 5", "KKKKK SSSSSSSSSSSSSSS N K N", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_2(self): self.assert_highlighted_chr_multi_line( @@ -1177,7 +1360,9 @@ def test_6_2(self): "KKKKK SSSSSSSSSSSSSSS N K N", "print '5 times 5 is ' 5 * 5", "KKKKK SSSSSSSSSSSSSSS N K N", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_3(self): self.assert_highlighted_chr_multi_line( @@ -1185,7 +1370,9 @@ def test_6_3(self): "TTTT K TTTT", "answer = 20 + 4", "TTTTTT K NN K N", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_4(self): self.assert_highlighted_chr_multi_line( @@ -1201,13 +1388,14 @@ def test_6_4(self): "TTTTT K TTTTT K N", "print verse ' bottles of beer on the wall'", "KKKKK TTTTT SSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_5(self): self.assert_highlighted_chr( - "print 'Baby shark'", - "KKKKK SSSSSSSSSSSS", - level="level6", lang='en') + "print 'Baby shark'", "KKKKK SSSSSSSSSSSS", level="level6", lang="en" + ) def test_6_6(self): self.assert_highlighted_chr_multi_line( @@ -1239,13 +1427,17 @@ def test_6_6(self): "KKKKKKK NN", "turn angle", "KKKK TTTTT", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_7(self): self.assert_highlighted_chr( "print 'Drawing figures'", "KKKKK SSSSSSSSSSSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_8(self): self.assert_highlighted_chr_multi_line( @@ -1261,7 +1453,9 @@ def test_6_8(self): "KK TTTTTTTTTT KK TTTT TTTTTTTTTTT K TTTTTTTTTTT K N", "print 'Emma will do the dishes this week' emma_washes 'times'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS TTTTTTTTTTT SSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_9(self): self.assert_highlighted_chr_multi_line( @@ -1281,13 +1475,17 @@ def test_6_9(self): "KKKKKK TTTTTTTTTT KKKK TTTTTT", "dishwasher = people at random", "TTTTTTTTTT K TTTTTT KK KKKKKK", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_10(self): self.assert_highlighted_chr( "print 'Who does the dishes?'", "KKKKK SSSSSSSSSSSSSSSSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_11(self): self.assert_highlighted_chr_multi_line( @@ -1303,13 +1501,17 @@ def test_6_11(self): "KK TTTTT KK TTTTTTTTT TTTTTT K TTTTTT K N KKKK TTTTTT K TTTTTT K TTTTT", "print 'those are' points ' point'", "KKKKK SSSSSSSSSSS TTTTTT SSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_12(self): self.assert_highlighted_chr( "print 'What will the die indicate this time?'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_13(self): self.assert_highlighted_chr_multi_line( @@ -1321,7 +1523,9 @@ def test_6_13(self): "KK TTTTTT KK TTTTTTTTTTTTTT KKKKK SSSSSSSSSSS", "else print 'Wrong! It was ' correct_answer", "KKKK KKKKK SSSSSSSSSSSSSSSS TTTTTTTTTTTTTT", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_14(self): self.assert_highlighted_chr_multi_line( @@ -1341,13 +1545,17 @@ def test_6_14(self): "KK TTTTTT KK TTTTTTTTTTTTTT KKKKK SSSSSS", "else print 'mistake! it was ' correct_answer", "KKKK KKKKK SSSSSSSSSSSSSSSSSS TTTTTTTTTTTTTT", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_15(self): self.assert_highlighted_chr( "print 'Welcome to this calculator!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_16(self): self.assert_highlighted_chr_multi_line( @@ -1373,7 +1581,9 @@ def test_6_16(self): "KKKKK SSSSSSSSSSSSSSS TTTTT SSSSSSSSSSSSSSSSSS", "print 'Thank you, enjoy your meal!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_17(self): self.assert_highlighted_chr_multi_line( @@ -1415,13 +1625,17 @@ def test_6_17(self): "KKKKK SSSSSSSSSSSSSSS TTTTT SSSSSSSSSSSSSSSSSS", "print 'Thank you, enjoy your meal!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_18(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_19(self): self.assert_highlighted_chr_multi_line( @@ -1461,7 +1675,9 @@ def test_6_19(self): "TTTTTT K TTTTTT K TTTTTTTT", "print 'You are ' result ' percent smart.'", "KKKKK SSSSSSSSSS TTTTTT SSSSSSSSSSSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_20(self): self.assert_highlighted_chr_multi_line( @@ -1473,19 +1689,25 @@ def test_6_20(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSS", "print 'happy birthday to you'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_6_21(self): self.assert_highlighted_chr( "print 'On to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSS", - level="level6", lang='en') + level="level6", + lang="en", + ) def test_7_1(self): self.assert_highlighted_chr( "repeat 3 times print 'Hedy is fun!'", "KKKKKK N KKKKK KKKKK SSSSSSSSSSSSSS", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_2(self): self.assert_highlighted_chr_multi_line( @@ -1495,13 +1717,17 @@ def test_7_2(self): "KKKKKK N KKKKK KKKKK SSSSSSS", "print 'Why is nobody helping me?'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_3(self): self.assert_highlighted_chr( "repeat 5 times print 'Help!'", "KKKKKK N KKKKK KKKKK SSSSSSS", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_4(self): self.assert_highlighted_chr_multi_line( @@ -1509,13 +1735,14 @@ def test_7_4(self): "KKKKKK I I KKKKK SSSSSSSSSSSSSSSSSSSSSSSSS", "print 'Baby Shark'", "KKKKK SSSSSSSSSSSS", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_5(self): self.assert_highlighted_chr( - "print 'Baby Shark'", - "KKKKK SSSSSSSSSSSS", - level="level7", lang='en') + "print 'Baby Shark'", "KKKKK SSSSSSSSSSSS", level="level7", lang="en" + ) def test_7_6(self): self.assert_highlighted_chr_multi_line( @@ -1523,7 +1750,9 @@ def test_7_6(self): "KKKKK SSSSSSSSSSSSSS", "repeat 3 times forward 10", "KKKKKK N KKKKK KKKKKKK NN", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_7(self): self.assert_highlighted_chr_multi_line( @@ -1531,13 +1760,17 @@ def test_7_7(self): "TTTTTT K TTTK TTTK TTTTK TTTTTT", "repeat _ _ print 'the dishwasher is' _", "KKKKKK I I KKKKK SSSSSSSSSSSSSSSSSSS I", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_8(self): self.assert_highlighted_chr( "print 'Who does the dishes?'", "KKKKK SSSSSSSSSSSSSSSSSSSSSS", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_9(self): self.assert_highlighted_chr_multi_line( @@ -1545,13 +1778,17 @@ def test_7_9(self): "TTTTTTT K NK NK NK NK NK TTTTTTTTT", "repeat _ _ print _ _ _", "KKKKKK I I KKKKK I I I", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_10(self): self.assert_highlighted_chr( "print 'What will the die indicate this time?'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_11(self): self.assert_highlighted_chr_multi_line( @@ -1563,13 +1800,17 @@ def test_7_11(self): "KKKKKK TTTTTT KKKKK TTTT K KKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSS", "print 'Thanks for your order! Its coming right up!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_12(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_13(self): self.assert_highlighted_chr_multi_line( @@ -1583,19 +1824,25 @@ def test_7_13(self): "TTTTTT K TTTK TTK TTTTT", "repeat 3 times print 'My crystal ball says... ' answer at random", "KKKKKK N KKKKK KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSS TTTTTT KK KKKKKK", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_14(self): self.assert_highlighted_chr( "repeat 5 times print 'In the next level you can repeat multiple lines of code at once!'", "KKKKKK N KKKKK KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_7_15(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level7", lang='en') + level="level7", + lang="en", + ) def test_8_1(self): self.assert_highlighted_chr_multi_line( @@ -1605,7 +1852,9 @@ def test_8_1(self): "KKKKK SSSSSSSSSSSSS", "print 'This will be printed 5 times'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_2(self): self.assert_highlighted_chr_multi_line( @@ -1615,7 +1864,9 @@ def test_8_2(self): "KKKKK SSSSSSSSSSSSSSSS", "print 'This is all repeated 5 times'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_3(self): self.assert_highlighted_chr_multi_line( @@ -1641,13 +1892,17 @@ def test_8_3(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", "print 'The T-rex closes in and eats him in one big bite!🦖'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_4(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_5(self): self.assert_highlighted_chr_multi_line( @@ -1665,7 +1920,9 @@ def test_8_5(self): "TTTTT K TTTTT K N", "print verse ' bottles of beer on the wall'", "KKKKK TTTTT SSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_6(self): self.assert_highlighted_chr_multi_line( @@ -1677,7 +1934,9 @@ def test_8_6(self): "KKKK TTTTT", "forward 50", "KKKKKKK NN", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_7(self): self.assert_highlighted_chr_multi_line( @@ -1691,13 +1950,17 @@ def test_8_7(self): "KKKK I", "forward _", "KKKKKKK I", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_8(self): self.assert_highlighted_chr( "hoeken = ask 'How many angles should I draw?'", "TTTTTT K KKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_9(self): self.assert_highlighted_chr_multi_line( @@ -1717,7 +1980,9 @@ def test_8_9(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSS", "print 'Enjoy your meal!'", "KKKKK SSSSSSSSSSSSSSSSSS", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_10(self): self.assert_highlighted_chr_multi_line( @@ -1737,7 +2002,9 @@ def test_8_10(self): "KKKKK", "print 'My crystal ball says...' answers at random", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSS TTTTTTT KK KKKKKK", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_11(self): self.assert_highlighted_chr_multi_line( @@ -1757,13 +2024,17 @@ def test_8_11(self): "KKKK", "print 'Okay, you can stay here for a little longer!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_8_12(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level8", lang='en') + level="level8", + lang="en", + ) def test_9_1(self): self.assert_highlighted_chr_multi_line( @@ -1779,7 +2050,9 @@ def test_9_1(self): "KKKK", "print 'pizza is better'", "KKKKK SSSSSSSSSSSSSSSSS", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_2(self): self.assert_highlighted_chr_multi_line( @@ -1807,7 +2080,9 @@ def test_9_2(self): "KKKK", "print 'Robin goes home'", "KKKKK SSSSSSSSSSSSSSSSS", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_3(self): self.assert_highlighted_chr_multi_line( @@ -1861,13 +2136,17 @@ def test_9_3(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", "print 'She walks back'", "KKKKK SSSSSSSSSSSSSSSS", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_4(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_5(self): self.assert_highlighted_chr_multi_line( @@ -1897,7 +2176,9 @@ def test_9_5(self): "KKKKK SSSSSSSSSSS", "# finish this code", "CCCCCCCCCCCCCCCCCC", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_6(self): self.assert_highlighted_chr_multi_line( @@ -1925,13 +2206,17 @@ def test_9_6(self): "TTTTT K TTTTT K N", "print 'Great job! Your score is... ' score ' out of 10!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSS TTTTT SSSSSSSSSSSSS", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_7(self): self.assert_highlighted_chr( "print 'Welcome to this calculator!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_8(self): self.assert_highlighted_chr_multi_line( @@ -1973,7 +2258,9 @@ def test_9_8(self): "KKKKK SSSSSSSSSSSSSSS TTTTT SSSSSSSSS", "print 'Enjoy your meal!'", "KKKKK SSSSSSSSSSSSSSSSSS", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_9(self): self.assert_highlighted_chr_multi_line( @@ -2013,13 +2300,17 @@ def test_9_9(self): "KK TTTTTT KK TTTTT", "print 'Great! You survived!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSS", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_10(self): self.assert_highlighted_chr( "print 'Escape from the haunted house!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_11(self): self.assert_highlighted_chr_multi_line( @@ -2031,13 +2322,17 @@ def test_9_11(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", "print 'if youre happy and you know it clap your hands'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_9_12(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level9", lang='en') + level="level9", + lang="en", + ) def test_10_1(self): self.assert_highlighted_chr_multi_line( @@ -2047,7 +2342,9 @@ def test_10_1(self): "KKK TTTTTT KK TTTTTTT", "print 'I love ' animal", "KKKKK SSSSSSSSS TTTTTT", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_2(self): self.assert_highlighted_chr_multi_line( @@ -2071,13 +2368,17 @@ def test_10_2(self): "KKKKK SSSSSSSSSSSSSSSSSS", "print 'I see all the animals looking at me!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_3(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_4(self): self.assert_highlighted_chr_multi_line( @@ -2139,7 +2440,9 @@ def test_10_4(self): "KKKKK SSSSSSSSSS TTTTT", "print 'everywhere a ' sound sound", "KKKKK SSSSSSSSSSSSSSS TTTTT TTTTT", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_5(self): self.assert_highlighted_chr_multi_line( @@ -2151,7 +2454,9 @@ def test_10_5(self): "KKK TTT KK TTTT", "print names at random ' does the dishes on ' day", "KKKKK TTTTT KK KKKKKK SSSSSSSSSSSSSSSSSSSSSS TTT", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_6(self): self.assert_highlighted_chr_multi_line( @@ -2165,7 +2470,9 @@ def test_10_6(self): "KKKKK TTTTTT SSSSSSSSSS TTTTTTT KK KKKKKK", "sleep", "KKKKK", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_7(self): self.assert_highlighted_chr_multi_line( @@ -2177,7 +2484,9 @@ def test_10_7(self): "KKK TTTTTT KK TTTTTTT", "print player ' chooses ' choices at random", "KKKKK TTTTTT SSSSSSSSSSS TTTTTTT KK KKKKKK", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_8(self): self.assert_highlighted_chr_multi_line( @@ -2199,7 +2508,9 @@ def test_10_8(self): "KKKK", "print 'Thats wrong. The right answer is ' correct", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS TTTTTTT", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_9(self): self.assert_highlighted_chr_multi_line( @@ -2211,7 +2522,9 @@ def test_10_9(self): "TTTT K KKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS TTTTTT SSS", "print food ' will be your ' course", "KKKKK TTTT SSSSSSSSSSSSSSSS TTTTTT", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_10(self): self.assert_highlighted_chr_multi_line( @@ -2227,13 +2540,17 @@ def test_10_10(self): "TTTT K KKK TTTT SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS TTTTTT SSS", "print name ' orders ' food ' as their ' course", "KKKKK TTTT SSSSSSSSSS TTTT SSSSSSSSSSSS TTTTTT", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_11(self): self.assert_highlighted_chr( "courses = appetizer, main course, dessert", "TTTTTTT K TTTTTTTTTK TTTTTTTTTTTK TTTTTTT", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_12(self): self.assert_highlighted_chr_multi_line( @@ -2255,7 +2572,9 @@ def test_10_12(self): "KKKKK TTTT SSSSSSSSSSSSSS TTTT KK KKKKKK SSSSSSSSSSSSSSSS", "sleep", "KKKKK", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_13(self): self.assert_highlighted_chr_multi_line( @@ -2275,13 +2594,17 @@ def test_10_13(self): "KKKKK TTTT SSSSSSSSSSSSSSS TTTTTTTT KK KKKKKK", "print name 's greatest fear is ' fears at random", "KKKKK TTTT SSSSSSSSSSSSSSSSSSSSS TTTTT KK KKKKKK", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_10_14(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level10", lang='en') + level="level10", + lang="en", + ) def test_11_1(self): self.assert_highlighted_chr_multi_line( @@ -2289,7 +2612,9 @@ def test_11_1(self): "KKK TTTTTTT KK KKKKK N KK N", "print counter", "KKKKK TTTTTTT", - level="level11", lang='en') + level="level11", + lang="en", + ) def test_11_2(self): self.assert_highlighted_chr_multi_line( @@ -2309,13 +2634,17 @@ def test_11_2(self): "KKKK", "print 'NO MORE MONKEYS JUMPING ON THE BED!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level11", lang='en') + level="level11", + lang="en", + ) def test_11_3(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level11", lang='en') + level="level11", + lang="en", + ) def test_11_4(self): self.assert_highlighted_chr_multi_line( @@ -2345,7 +2674,9 @@ def test_11_4(self): "TTTTT K N K TTTTTT", "print 'That will be ' price ' dollars, please!'", "KKKKK SSSSSSSSSSSSSSS TTTTT SSSSSSSSSSSSSSSSSSS", - level="level11", lang='en') + level="level11", + lang="en", + ) def test_11_5(self): self.assert_highlighted_chr_multi_line( @@ -2387,13 +2718,17 @@ def test_11_5(self): "KK TTTTTT KK TTTTT", "print 'Great! You survived!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSS", - level="level11", lang='en') + level="level11", + lang="en", + ) def test_11_6(self): self.assert_highlighted_chr( "print 'Escape from the haunted house!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level11", lang='en') + level="level11", + lang="en", + ) def test_11_7(self): self.assert_highlighted_chr_multi_line( @@ -2405,13 +2740,17 @@ def test_11_7(self): "TTTT K TTTTTTTTTTTTTTTTTTTTT", "print show 'is my favorite show!'", "KKKKK TTTT SSSSSSSSSSSSSSSSSSSSSS", - level="level11", lang='en') + level="level11", + lang="en", + ) def test_11_8(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level11", lang='en') + level="level11", + lang="en", + ) def test_12_1(self): self.assert_highlighted_chr_multi_line( @@ -2419,7 +2758,9 @@ def test_12_1(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", "print 2.5 + 2.5", "KKKKK NNN K NNN", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_2(self): self.assert_highlighted_chr_multi_line( @@ -2427,7 +2768,9 @@ def test_12_2(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", "print 2.5 + 2.5", "KKKKK NNN K NNN", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_3(self): self.assert_highlighted_chr_multi_line( @@ -2435,7 +2778,9 @@ def test_12_3(self): "TTTT K SSSSSSSSSSSSSSSS", "print 'Hello ' name", "KKKKK SSSSSSSS TTTT", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_4(self): self.assert_highlighted_chr_multi_line( @@ -2443,7 +2788,9 @@ def test_12_4(self): "TTTTTTTTTTT K SSSSSSSSSSSK SSSSSSSSK SSSSSSSSSS", "print superheroes at random", "KKKKK TTTTTTTTTTT KK KKKKKK", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_5(self): self.assert_highlighted_chr_multi_line( @@ -2453,7 +2800,9 @@ def test_12_5(self): "KK TTTT K SSSSSSSSSSSSSSSS", "print 'Hi there!'", "KKKKK SSSSSSSSSSS", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_6(self): self.assert_highlighted_chr_multi_line( @@ -2461,7 +2810,9 @@ def test_12_6(self): "TTTTT K NN", "print 'You got ' score", "KKKKK SSSSSSSSSS TTTTT", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_7(self): self.assert_highlighted_chr_multi_line( @@ -2471,7 +2822,9 @@ def test_12_7(self): "T K SSSSSSSS", "print a + b", "KKKKK T K T", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_8(self): self.assert_highlighted_chr_multi_line( @@ -2479,13 +2832,17 @@ def test_12_8(self): "TTTT K SSSSSSSSSSSSSSSSSSSSSS", "print name ' was eating a piece of cake, when suddenly...'", "KKKKK TTTT SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_9(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_10(self): self.assert_highlighted_chr_multi_line( @@ -2505,7 +2862,9 @@ def test_12_10(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", "print action", "KKKKK TTTTTT", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_11(self): self.assert_highlighted_chr_multi_line( @@ -2517,7 +2876,9 @@ def test_12_11(self): "TTTTTT K TTTTTTT K TTTTTTT", "print number1 ' plus ' number2 ' is ' answer", "KKKKK TTTTTTT SSSSSSSS TTTTTTT SSSSSS TTTTTT", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_12(self): self.assert_highlighted_chr_multi_line( @@ -2545,7 +2906,9 @@ def test_12_12(self): "TTTTT K TTTTT K NNNN", "print 'That will be ' price ' dollar, please'", "KKKKK SSSSSSSSSSSSSSS TTTTT SSSSSSSSSSSSSSSSS", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_13(self): self.assert_highlighted_chr_multi_line( @@ -2559,7 +2922,9 @@ def test_12_13(self): "KKKKK", "print fortunes at random", "KKKKK TTTTTTTT KK KKKKKK", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_14(self): self.assert_highlighted_chr_multi_line( @@ -2579,7 +2944,9 @@ def test_12_14(self): "TTTTT K TTTTTTT K TTTTTTTTT", "print 'You can buy a ' wish ' in ' weeks ' weeks.'", "KKKKK SSSSSSSSSSSSSSSS TTTT SSSSSS TTTTT SSSSSSSSS", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_15(self): self.assert_highlighted_chr_multi_line( @@ -2605,13 +2972,17 @@ def test_12_15(self): "T KK SSSSSSSSSSSSSSSS", "print a + b", "KKKKK T K T", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_16(self): self.assert_highlighted_chr( "## place your code here", "CCCCCCCCCCCCCCCCCCCCCCC", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_17(self): self.assert_highlighted_chr_multi_line( @@ -2633,13 +3004,17 @@ def test_12_17(self): "KKKK", "print 'Access denied!'", "KKKKK SSSSSSSSSSSSSSSS", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_12_18(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level12", lang='en') + level="level12", + lang="en", + ) def test_13_1(self): self.assert_highlighted_chr_multi_line( @@ -2651,7 +3026,9 @@ def test_13_1(self): "KK TTTT KK SSSSSS KKK TTT KK N", "print 'You are the real Hedy!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSS", - level="level13", lang='en') + level="level13", + lang="en", + ) def test_13_2(self): self.assert_highlighted_chr_multi_line( @@ -2701,13 +3078,17 @@ def test_13_2(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", "sword = 'found'", "TTTTT K SSSSSSS", - level="level13", lang='en') + level="level13", + lang="en", + ) def test_13_3(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level13", lang='en') + level="level13", + lang="en", + ) def test_13_4(self): self.assert_highlighted_chr_multi_line( @@ -2733,7 +3114,9 @@ def test_13_4(self): "KK TTTTTTTTTTTTTTT KK SSSSSS KKK TTTTTTTTTTT KK SSSSSSSSSS", "print 'The computer wins!'", "KKKKK SSSSSSSSSSSSSSSSSSSS", - level="level13", lang='en') + level="level13", + lang="en", + ) def test_13_5(self): self.assert_highlighted_chr_multi_line( @@ -2751,7 +3134,9 @@ def test_13_5(self): "TTTTT K TTTTT K N", "print 'That will be ' price ' dollars'", "KKKKK SSSSSSSSSSSSSSS TTTTT SSSSSSSSSS", - level="level13", lang='en') + level="level13", + lang="en", + ) def test_13_6(self): self.assert_highlighted_chr_multi_line( @@ -2761,7 +3146,9 @@ def test_13_6(self): "KK TTTTTT KK SSSSSSS KK TTTTTT KK SSSSSSS", "print 'Thats a healthy choice'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSS", - level="level13", lang='en') + level="level13", + lang="en", + ) def test_13_7(self): self.assert_highlighted_chr_multi_line( @@ -2777,13 +3164,17 @@ def test_13_7(self): "KKKK", "print 'Go to the trainstation at 10.00'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level13", lang='en') + level="level13", + lang="en", + ) def test_13_8(self): self.assert_highlighted_chr( "## place your code here", "CCCCCCCCCCCCCCCCCCCCCCC", - level="level13", lang='en') + level="level13", + lang="en", + ) def test_13_9(self): self.assert_highlighted_chr_multi_line( @@ -2803,13 +3194,17 @@ def test_13_9(self): "KKKK", "print 'Great! You have passed the subject!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level13", lang='en') + level="level13", + lang="en", + ) def test_13_10(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level13", lang='en') + level="level13", + lang="en", + ) def test_14_1(self): self.assert_highlighted_chr_multi_line( @@ -2823,7 +3218,9 @@ def test_14_1(self): "KKKK", "print 'You are older than me!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSS", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_14_2(self): self.assert_highlighted_chr_multi_line( @@ -2833,7 +3230,9 @@ def test_14_2(self): "KK TTT K NN", "print 'You are older than I am!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_14_3(self): self.assert_highlighted_chr_multi_line( @@ -2843,7 +3242,9 @@ def test_14_3(self): "KK TTTT KK SSSSSS", "print 'You are coo!'", "KKKKK SSSSSSSSSSSSSS", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_14_4(self): self.assert_highlighted_chr_multi_line( @@ -2853,7 +3254,9 @@ def test_14_4(self): "KK TTTT KK SSSSSS", "print 'You are not Hedy'", "KKKKK SSSSSSSSSSSSSSSSSS", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_14_5(self): self.assert_highlighted_chr_multi_line( @@ -2885,13 +3288,17 @@ def test_14_5(self): "KKKKK SSSSSSSSSS", "game = 'over'", "TTTT K SSSSSS", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_14_6(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_14_7(self): self.assert_highlighted_chr_multi_line( @@ -2935,7 +3342,9 @@ def test_14_7(self): "KKKK", "print 'GAME OVER'", "KKKKK SSSSSSSSSSS", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_14_8(self): self.assert_highlighted_chr_multi_line( @@ -2961,7 +3370,9 @@ def test_14_8(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSS", "print 'Lets go shopping!'", "KKKKK SSSSSSSSSSSSSSSSSSS", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_14_9(self): self.assert_highlighted_chr_multi_line( @@ -2999,7 +3410,9 @@ def test_14_9(self): "KK TTTTTTTT K TTTTTTTT", "print 'You belong to the B club'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_14_10(self): self.assert_highlighted_chr_multi_line( @@ -3019,13 +3432,17 @@ def test_14_10(self): "KK TTTTTT KK SSSSS", "print 'Ok we will continue'", "KKKKK SSSSSSSSSSSSSSSSSSSSS", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_14_11(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level14", lang='en') + level="level14", + lang="en", + ) def test_15_1(self): self.assert_highlighted_chr_multi_line( @@ -3037,7 +3454,9 @@ def test_15_1(self): "TTTTTT K KKK SSSSSSSSSSSSSSSSSSSS", "print 'A correct answer has been given'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level15", lang='en') + level="level15", + lang="en", + ) def test_15_2(self): self.assert_highlighted_chr_multi_line( @@ -3065,13 +3484,17 @@ def test_15_2(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSS TTTTTTTT", "print 'Now you can enter the house!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level15", lang='en') + level="level15", + lang="en", + ) def test_15_3(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level15", lang='en') + level="level15", + lang="en", + ) def test_15_4(self): self.assert_highlighted_chr_multi_line( @@ -3093,7 +3516,9 @@ def test_15_4(self): "TTTTT K TTTTT K N", "print 'Yes! You have thrown 6 in ' tries ' tries.'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS TTTTT SSSSSSSSS", - level="level15", lang='en') + level="level15", + lang="en", + ) def test_15_5(self): self.assert_highlighted_chr_multi_line( @@ -3125,7 +3550,9 @@ def test_15_5(self): "KKKKK SSSSSSSSSS", "won = 'yes'", "TTT K SSSSS", - level="level15", lang='en') + level="level15", + lang="en", + ) def test_15_6(self): self.assert_highlighted_chr_multi_line( @@ -3155,7 +3582,9 @@ def test_15_6(self): "KKKKK SSSSSSSSSSS", "print 'You win!'", "KKKKK SSSSSSSSSS", - level="level15", lang='en') + level="level15", + lang="en", + ) def test_15_7(self): self.assert_highlighted_chr_multi_line( @@ -3173,7 +3602,9 @@ def test_15_7(self): "TTTT K KKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", "print 'Thank you!'", "KKKKK SSSSSSSSSSSS", - level="level15", lang='en') + level="level15", + lang="en", + ) def test_15_8(self): self.assert_highlighted_chr_multi_line( @@ -3195,13 +3626,17 @@ def test_15_8(self): "KKK TTTTT KK TTTTTT", "print 'A ' animal ' says ' sound", "KKKKK SSSS TTTTTT SSSSSSSS TTTTT", - level="level15", lang='en') + level="level15", + lang="en", + ) def test_15_9(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level15", lang='en') + level="level15", + lang="en", + ) def test_16_1(self): self.assert_highlighted_chr_multi_line( @@ -3209,7 +3644,9 @@ def test_16_1(self): "TTTTT K KSSSSSSSK SSSSSSSSK SSSSSSSSK", "print fruit", "KKKKK TTTTT", - level="level16", lang='en') + level="level16", + lang="en", + ) def test_16_2(self): self.assert_highlighted_chr_multi_line( @@ -3223,7 +3660,9 @@ def test_16_2(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSS TTTTTTTKTK", "print 'is ' lucky_numbers[i]", "KKKKK SSSSS TTTTTTTTTTTTTKTK", - level="level16", lang='en') + level="level16", + lang="en", + ) def test_16_3(self): self.assert_highlighted_chr_multi_line( @@ -3271,13 +3710,17 @@ def test_16_3(self): "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSS", "print 'early in the morning'", "KKKKK SSSSSSSSSSSSSSSSSSSSSS", - level="level16", lang='en') + level="level16", + lang="en", + ) def test_16_4(self): self.assert_highlighted_chr( "# place your code here", "CCCCCCCCCCCCCCCCCCCCCC", - level="level16", lang='en') + level="level16", + lang="en", + ) def test_16_5(self): self.assert_highlighted_chr_multi_line( @@ -3329,7 +3772,9 @@ def test_16_5(self): "KKKKK TTTTTTTTKTK", "print 'GAME OVER'", "KKKKK SSSSSSSSSSS", - level="level16", lang='en') + level="level16", + lang="en", + ) def test_16_6(self): self.assert_highlighted_chr_multi_line( @@ -3357,7 +3802,9 @@ def test_16_6(self): "KKKKK SSSSSSSSS TTTTTTTTTTTTKTK SSSSSSSSS TTTTTTTTTTTKTK", "print 'You gave ' score ' correct answers.'", "KKKKK SSSSSSSSSSS TTTTT SSSSSSSSSSSSSSSSSSS", - level="level16", lang='en') + level="level16", + lang="en", + ) def test_16_7(self): self.assert_highlighted_chr_multi_line( @@ -3375,13 +3822,17 @@ def test_16_7(self): "KKKK", "print 'Yikes...'", "KKKKK SSSSSSSSSS", - level="level16", lang='en') + level="level16", + lang="en", + ) def test_16_8(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level16", lang='en') + level="level16", + lang="en", + ) def test_17_1(self): self.assert_highlighted_chr_multi_line( @@ -3391,7 +3842,9 @@ def test_17_1(self): "KKKKK T", "print 'Ready or not, here I come!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level17", lang='en') + level="level17", + lang="en", + ) def test_17_2(self): self.assert_highlighted_chr_multi_line( @@ -3413,13 +3866,17 @@ def test_17_2(self): "KKKKK", "print 'Better luck next time..'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSS", - level="level17", lang='en') + level="level17", + lang="en", + ) def test_17_3(self): self.assert_highlighted_chr( "print 'Lets go to the next level!'", "KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSS", - level="level17", lang='en') + level="level17", + lang="en", + ) def test_18_1(self): self.assert_highlighted_chr_multi_line( @@ -3427,7 +3884,9 @@ def test_18_1(self): "TTTT K SSSSSS", "print('My name is ', naam)", "KKKKKKSSSSSSSSSSSSSK TTTTK", - level="level18", lang='en') + level="level18", + lang="en", + ) def test_18_2(self): self.assert_highlighted_chr_multi_line( @@ -3437,10 +3896,14 @@ def test_18_2(self): "TTTT K SSSSSS", "print('my name is ', name)", "KKKKKKSSSSSSSSSSSSSK TTTTK", - level="level18", lang='en') + level="level18", + lang="en", + ) def test_18_3(self): self.assert_highlighted_chr( "print ('Great job!!!')", "KKKKK KSSSSSSSSSSSSSSK", - level="level18", lang='en') + level="level18", + lang="en", + ) diff --git a/tests/test_highlighting/test_affectation.py b/tests/test_highlighting/test_affectation.py index 6fa4c77b4ed..00906bfaca1 100644 --- a/tests/test_highlighting/test_affectation.py +++ b/tests/test_highlighting/test_affectation.py @@ -4,64 +4,67 @@ class HighlighterTestAffectation(HighlightTester): - - @parameterized.expand([ - ("level2"), - ("level3"), - ("level4"), - ]) + @parameterized.expand( + [ + ("level2"), + ("level3"), + ("level4"), + ] + ) def test_is(self, level): self.assert_highlighted_chr( - "sword is lost", - "TTTTT KK TTTT", - level=level, lang="en") + "sword is lost", "TTTTT KK TTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_is_string(self, level): self.assert_highlighted_chr( - "sword is 'lost'", - "TTTTT KK SSSSSS", - level=level, lang="en") + "sword is 'lost'", "TTTTT KK SSSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level4"), - ]) + @parameterized.expand( + [ + ("level4"), + ] + ) def test_is_string_unquote(self, level): self.assert_highlighted_chr( - "sword is 'lost'", - "TTTTT KK TTTTTT", - level=level, lang="en") + "sword is 'lost'", "TTTTT KK TTTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_is_string_equal(self, level): self.assert_highlighted_chr( - "sword = 'lost'", - "TTTTT K SSSSSS", - level=level, lang="en") + "sword = 'lost'", "TTTTT K SSSSSS", level=level, lang="en" + ) diff --git a/tests/test_highlighting/test_arabic.py b/tests/test_highlighting/test_arabic.py index dbd563bb1bf..fb52f7b3b09 100644 --- a/tests/test_highlighting/test_arabic.py +++ b/tests/test_highlighting/test_arabic.py @@ -4,178 +4,203 @@ class HighlighterTestPrintArabic(HighlightTester): - - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ] + ) def test_print1(self, level): self.assert_highlighted_chr( - "قول مرحبا أيها العالم!", - "KKK TTTTTTTTTTTTTTTTTT", - level=level, lang='ar') + "قول مرحبا أيها العالم!", "KKK TTTTTTTTTTTTTTTTTT", level=level, lang="ar" + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print2(self, level): self.assert_highlighted_chr( - 'قول "مرحبا أيها العالم"', - "KKK SSSSSSSSSSSSSSSSSSS", - level=level, lang='ar') + 'قول "مرحبا أيها العالم"', "KKK SSSSSSSSSSSSSSSSSSS", level=level, lang="ar" + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_random_alone(self, level): self.assert_highlighted_chr( "قول حيواناتي بشكل عشوائي", "KKK TTTTTTTT KKKK KKKKKK", - level=level, lang='ar') + level=level, + lang="ar", + ) def test_print_random1(self): self.assert_highlighted_chr( "قول حيواناتي بشكل عشوائي مرحبا أيها العالم!", "KKK TTTTTTTT KKKK KKKKKK TTTTTTTTTTTTTTTTTT", - level="level3", lang='ar') + level="level3", + lang="ar", + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_random2(self, level): self.assert_highlighted_chr( 'قول حيواناتي بشكل عشوائي "مرحبا أيها العالم!"', "KKK TTTTTTTT KKKK KKKKKK SSSSSSSSSSSSSSSSSSSS", - level=level, lang='ar') + level=level, + lang="ar", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_is_number_equal_plus(self, level): self.assert_highlighted_chr( - "sword = ١٢٣٤٦ + ٧٨٥٦٤", - "TTTTT K NNNNN K NNNNN", - level=level, lang="ar") + "sword = ١٢٣٤٦ + ٧٨٥٦٤", "TTTTT K NNNNN K NNNNN", level=level, lang="ar" + ) - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_is_number_equal_float_plus(self, level): self.assert_highlighted_chr( - "sword = ١٢٣.٤٦ + ٧٩٤٩.٨", - "TTTTT K NNNNNN K NNNNNN", - level=level, lang="ar") + "sword = ١٢٣.٤٦ + ٧٩٤٩.٨", "TTTTT K NNNNNN K NNNNNN", level=level, lang="ar" + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_mul(self, level): self.assert_highlighted_chr( 'قول "مرحبا أيها العالم" ١٢٣٤٦ * ٧٩٤٩٨', "KKK SSSSSSSSSSSSSSSSSSS NNNNN K NNNNN", - level=level, lang='ar') + level=level, + lang="ar", + ) - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_div_float(self, level): self.assert_highlighted_chr( 'قول "مرحبا أيها العالم" ١٢٣.٤٦ / ٧٩٤٩.٨', "KKK SSSSSSSSSSSSSSSSSSS NNNNNN K NNNNNN", - level=level, lang='ar') + level=level, + lang="ar", + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_comma(self, level): self.assert_highlighted_chr( - 'الحيوانات هو كلب، قطة، جمل', + "الحيوانات هو كلب، قطة، جمل", "TTTTTTTTT KK TTTK TTTK TTT", - level=level, lang='ar') + level=level, + lang="ar", + ) diff --git a/tests/test_highlighting/test_ask_echo.py b/tests/test_highlighting/test_ask_echo.py index 36ab4455086..855611e9878 100644 --- a/tests/test_highlighting/test_ask_echo.py +++ b/tests/test_highlighting/test_ask_echo.py @@ -2,33 +2,28 @@ class HighlighterTestAskEcho(HighlightTester): - def test_echo_alone(self): - self.assert_highlighted_chr( - "echo", - "KKKK", - level="level1", lang="en") + self.assert_highlighted_chr("echo", "KKKK", level="level1", lang="en") def test_echo1(self): self.assert_highlighted_chr( - "echo aavhzrbz", - "KKKK TTTTTTTT", - level="level1", lang="en") + "echo aavhzrbz", "KKKK TTTTTTTT", level="level1", lang="en" + ) def test_echo2(self): self.assert_highlighted_chr( - "echo Jouw naam is", - "KKKK TTTTTTTTTTTT", - level="level1", lang="en") + "echo Jouw naam is", "KKKK TTTTTTTTTTTT", level="level1", lang="en" + ) def test_ask1(self): self.assert_highlighted_chr( - "ask aavhzrbz", - "KKK TTTTTTTT", - level="level1", lang="en") + "ask aavhzrbz", "KKK TTTTTTTT", level="level1", lang="en" + ) def test_ask2(self): self.assert_highlighted_chr( "ask What would you like to order?", "KKK TTTTTTTTTTTTTTTTTTTTTTTTTTTTT", - level="level1", lang="en") + level="level1", + lang="en", + ) diff --git a/tests/test_highlighting/test_chinese.py b/tests/test_highlighting/test_chinese.py index 9fcaaff98fe..09569452dac 100644 --- a/tests/test_highlighting/test_chinese.py +++ b/tests/test_highlighting/test_chinese.py @@ -4,87 +4,93 @@ class HighlighterTestPrintChinese(HighlightTester): - - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ] + ) def test_print1(self, level): - self.assert_highlighted_chr( - "打印 你好世界!", - "KK TTTTT", - level=level, lang='zh_Hans') + self.assert_highlighted_chr("打印 你好世界!", "KK TTTTT", level=level, lang="zh_Hans") - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print2(self, level): self.assert_highlighted_chr( - "打印 '从现在开始你们需要使用引号!'", - "KK SSSSSSSSSSSSSSSS", - level=level, lang='zh_Hans') + "打印 '从现在开始你们需要使用引号!'", "KK SSSSSSSSSSSSSSSS", level=level, lang="zh_Hans" + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_random_alone(self, level): self.assert_highlighted_chr( - "打印 动物们 在 随机", - "KK TTT K KK", - level=level, lang='zh_Hans') + "打印 动物们 在 随机", "KK TTT K KK", level=level, lang="zh_Hans" + ) def test_print_random1(self): self.assert_highlighted_chr( "打印 从现在开始我们需要使用什么? 答案 在 随机", "KK TTTTTTTTTTTTTT TT K KK", - level="level3", lang='zh_Hans') + level="level3", + lang="zh_Hans", + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_random2(self, level): self.assert_highlighted_chr( "打印 '从现在开始我们需要使用什么?' 答案 在 随机", "KK SSSSSSSSSSSSSSSS TT K KK", - level=level, lang='zh_Hans') + level=level, + lang="zh_Hans", + ) diff --git a/tests/test_highlighting/test_comment.py b/tests/test_highlighting/test_comment.py index 9be29722ea0..ac680f5d776 100644 --- a/tests/test_highlighting/test_comment.py +++ b/tests/test_highlighting/test_comment.py @@ -4,29 +4,32 @@ class HighlighterTestComment(HighlightTester): - - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_comment(self, level): self.assert_highlighted_chr( "# Maak jouw eigen code hier", "CCCCCCCCCCCCCCCCCCCCCCCCCCC", - level=level, lang="en") + level=level, + lang="en", + ) diff --git a/tests/test_highlighting/test_for.py b/tests/test_highlighting/test_for.py index ce612100a45..fd8d23e89cf 100644 --- a/tests/test_highlighting/test_for.py +++ b/tests/test_highlighting/test_for.py @@ -4,52 +4,57 @@ class HighlighterTestFor(HighlightTester): - - @parameterized.expand([ - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_for(self, level): self.assert_highlighted_chr( - "for animal in animals", - "KKK TTTTTT KK TTTTTTT", - level=level, lang="en") + "for animal in animals", "KKK TTTTTT KK TTTTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_for_range1(self, level): self.assert_highlighted_chr( - "for i in range 1 to 3", - "KKK T KK KKKKK N KK N", - level=level, lang="en") + "for i in range 1 to 3", "KKK T KK KKKKK N KK N", level=level, lang="en" + ) - @parameterized.expand([ - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_for_range2(self, level): self.assert_highlighted_chr( "for i in range 1 to people", "KKK T KK KKKKK N KK TTTTTT", - level=level, lang="en") + level=level, + lang="en", + ) diff --git a/tests/test_highlighting/test_functions.py b/tests/test_highlighting/test_functions.py index 1769838abca..277bcf21f38 100644 --- a/tests/test_highlighting/test_functions.py +++ b/tests/test_highlighting/test_functions.py @@ -4,13 +4,15 @@ class HighlighterTestFunctions(HighlightTester): - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ] + ) def test_functions_1(self, level): self.assert_highlighted_chr_multi_line( "define function_1 with par1, par2", @@ -19,7 +21,9 @@ def test_functions_1(self, level): " KKKKKK TTTT", "call function_1 with 7, 'm'", "FFFF TTTTTTTTTT KKKK NK SSS", - level=level, lang="en") + level=level, + lang="en", + ) def test_functions_2(self): self.assert_highlighted_chr_multi_line( @@ -29,7 +33,9 @@ def test_functions_2(self): " KKKKKK TTTT", "call function_1 with 7, 'm'", "FFFF TTTTTTTTTT KKKK NK SSS", - level='level17', lang="en") + level="level17", + lang="en", + ) def test_functions_3(self): self.assert_highlighted_chr_multi_line( @@ -39,4 +45,6 @@ def test_functions_3(self): " KKKKKKTTTTK", "function_1(7, 'df')", "TTTTTTTTTTKNK SSSSK", - level='level18', lang="en") + level="level18", + lang="en", + ) diff --git a/tests/test_highlighting/test_highlighting_simulation.py b/tests/test_highlighting/test_highlighting_simulation.py index 213e22201e5..403a84cf5dd 100644 --- a/tests/test_highlighting/test_highlighting_simulation.py +++ b/tests/test_highlighting/test_highlighting_simulation.py @@ -1,17 +1,70 @@ from tests.Highlighter import HighlightTester RULES = { - "rules1": {'start': [{'regex': 'b', 'token': 'keyword', 'next': 'StateB'}, {'regex': 'a', 'token': 'constant.character', 'next': 'StateA'}, {'regex': '[^abc]', 'token': 'comment', 'next': 'start'}], 'StateA': [{'regex': '[^c]', 'token': 'invalid', 'next': 'StateA'}, {'regex': 'c', 'token': 'keyword', 'next': 'StateA'}], 'StateB': [{'regex': '[^c]', 'token': 'variable', 'next': 'StateB'}]}, - "rules2": {'start': [{'regex': 'b', 'token': 'keyword', 'next': 'StateB'}, {'regex': 'ba', 'token': 'constant.character', 'next': 'StateA'}, {'regex': '[^abcd]', 'token': 'comment', 'next': 'start'}], 'StateA': [{'regex': '[^c]', 'token': 'invalid', 'next': 'StateA'}, {'regex': 'c', 'token': 'keyword', 'next': 'StateA'}], 'StateB': [{'regex': '[^c]', 'token': 'variable', 'next': 'StateB'}]}, - "rules3": {'start': [{'regex': 'ba', 'token': 'constant.character', 'next': 'StateA'}, {'regex': 'b', 'token': 'keyword', 'next': 'StateB'}, {'regex': '[^abcd]', 'token': 'comment', 'next': 'start'}], 'StateA': [{'regex': '[^c]', 'token': 'invalid', 'next': 'StateA'}, {'regex': 'c', 'token': 'keyword', 'next': 'StateA'}], 'StateB': [{'regex': '[^c]', 'token': 'variable', 'next': 'StateB'}]}, - "rules4": {'start': [{'regex': '[ac]', 'token': 'keyword', 'next': 'StateB'}, {'regex': '[bc]', 'token': 'constant.character', 'next': 'StateA'}, {'regex': 'tie\nmen', 'token': 'variable', 'next': 'start'}, {'regex': '[^abcd]', 'token': 'comment', 'next': 'start'}], 'StateA': [{'regex': '[^c]', 'token': 'invalid', 'next': 'StateA'}, {'regex': 'c', 'token': 'keyword', 'next': 'StateA'}, {'regex': '$', 'token': 'invalid', 'next': 'StateE'}], 'StateB': [{'regex': '[^c]', 'token': 'variable', 'next': 'StateB'}, {'regex': '$', 'token': 'invalid', 'next': 'StateE'}], 'StateE': [{'regex': 'e', 'token': 'variable', 'next': 'start'}, {'regex': 'Test', 'token': 'comment'}]}, + "rules1": { + "start": [ + {"regex": "b", "token": "keyword", "next": "StateB"}, + {"regex": "a", "token": "constant.character", "next": "StateA"}, + {"regex": "[^abc]", "token": "comment", "next": "start"}, + ], + "StateA": [ + {"regex": "[^c]", "token": "invalid", "next": "StateA"}, + {"regex": "c", "token": "keyword", "next": "StateA"}, + ], + "StateB": [{"regex": "[^c]", "token": "variable", "next": "StateB"}], + }, + "rules2": { + "start": [ + {"regex": "b", "token": "keyword", "next": "StateB"}, + {"regex": "ba", "token": "constant.character", "next": "StateA"}, + {"regex": "[^abcd]", "token": "comment", "next": "start"}, + ], + "StateA": [ + {"regex": "[^c]", "token": "invalid", "next": "StateA"}, + {"regex": "c", "token": "keyword", "next": "StateA"}, + ], + "StateB": [{"regex": "[^c]", "token": "variable", "next": "StateB"}], + }, + "rules3": { + "start": [ + {"regex": "ba", "token": "constant.character", "next": "StateA"}, + {"regex": "b", "token": "keyword", "next": "StateB"}, + {"regex": "[^abcd]", "token": "comment", "next": "start"}, + ], + "StateA": [ + {"regex": "[^c]", "token": "invalid", "next": "StateA"}, + {"regex": "c", "token": "keyword", "next": "StateA"}, + ], + "StateB": [{"regex": "[^c]", "token": "variable", "next": "StateB"}], + }, + "rules4": { + "start": [ + {"regex": "[ac]", "token": "keyword", "next": "StateB"}, + {"regex": "[bc]", "token": "constant.character", "next": "StateA"}, + {"regex": "tie\nmen", "token": "variable", "next": "start"}, + {"regex": "[^abcd]", "token": "comment", "next": "start"}, + ], + "StateA": [ + {"regex": "[^c]", "token": "invalid", "next": "StateA"}, + {"regex": "c", "token": "keyword", "next": "StateA"}, + {"regex": "$", "token": "invalid", "next": "StateE"}, + ], + "StateB": [ + {"regex": "[^c]", "token": "variable", "next": "StateB"}, + {"regex": "$", "token": "invalid", "next": "StateE"}, + ], + "StateE": [ + {"regex": "e", "token": "variable", "next": "start"}, + {"regex": "Test", "token": "comment"}, + ], + }, } class HighlighterTestLeveLSimulation(HighlightTester): - - def assert_highlighted_chr(self, code, expected, rule_name, start_token="start", last_state="start"): - + def assert_highlighted_chr( + self, code, expected, rule_name, start_token="start", last_state="start" + ): rules = RULES[rule_name] self.check(code, expected, rules, start_token, last_state) @@ -20,40 +73,54 @@ def test_1(self): self.assert_highlighted_chr( "qmczqdqaqmcqbadqcc", "CCTCCCCSIIKIIIIIKK", - rule_name="rules1", last_state="StateA") + rule_name="rules1", + last_state="StateA", + ) def test_2(self): self.assert_highlighted_chr( "qmczqdqbqmcqbadqcc", "CCTCCCCKNNTNNNNNTT", - rule_name="rules1", last_state="StateB") + rule_name="rules1", + last_state="StateB", + ) def test_3(self): self.assert_highlighted_chr( "qmaaqgbeveqmcbazqdqaqmcqbadqcc", "CCTTCCKNNNNNTNNNNNNNNNTNNNNNTT", - rule_name="rules2", last_state="StateB") + rule_name="rules2", + last_state="StateB", + ) def test_4(self): self.assert_highlighted_chr( "qmaaqgbaeveqmcbazqdqaqmcqbadqcc", "CCTTCCKNNNNNNTNNNNNNNNNTNNNNNTT", - rule_name="rules2", last_state="StateB") + rule_name="rules2", + last_state="StateB", + ) def test_5(self): self.assert_highlighted_chr( "qmaaqgbeveqmcbazqdqaqmcqbadqcc", "CCTTCCKNNNNNTNNNNNNNNNTNNNNNTT", - rule_name="rules3", last_state="StateB") + rule_name="rules3", + last_state="StateB", + ) def test_6(self): self.assert_highlighted_chr( "qmaaqgbaeveqmcbazqdqaqmcqbadqcc", "CCTTCCSSIIIIIKIIIIIIIIIKIIIIIKK", - rule_name="rules3", last_state="StateA") + rule_name="rules3", + last_state="StateA", + ) def test_7(self): self.assert_highlighted_chr( "qmczqdqaqmcqbadq\ngrbnorbTestananaeqwswsmsazqdqaqmcqbadqcc\ngrbnorbTestananaeqwswsmsazqdqaqmcqbadqcc\n\ngrbnorbTestananaeqwswsmsbzqdqaqmcqbadqcc\ngrbnorbTestananaeqwswsmsazqdqaqmcqbadqcc\n\ngrbnorbTestananaeqwswsmsbzqdqaqmcqbadqcc", "CCKNNNNNNNTNNNNN\nNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNTNNNNNTT\nTTTTTTTCCCCTTTTTNCCCCCCCKNNNNNNNTNNNNNTT\n\nTTTTTTTCCCCTTTTTNCCCCCCCSIIIIIIIKIIIIIKK\nIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIKIIIIIKK\n\nTTTTTTTCCCCTTTTTNCCCCCCCSIIIIIIIKIIIIIKK", - rule_name="rules4", last_state="StateA") + rule_name="rules4", + last_state="StateA", + ) diff --git a/tests/test_highlighting/test_if_block.py b/tests/test_highlighting/test_if_block.py index 826b409eccf..8d39ec74cb0 100644 --- a/tests/test_highlighting/test_if_block.py +++ b/tests/test_highlighting/test_if_block.py @@ -4,321 +4,353 @@ class HighlighterTestIfBlock(HighlightTester): - - @parameterized.expand([ - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_is1(self, level): self.assert_highlighted_chr( - "if answer is yes", - "KK TTTTTT KK TTT", - level=level, lang="en") + "if answer is yes", "KK TTTTTT KK TTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_is2(self, level): self.assert_highlighted_chr( - "if answer is 'yes'", - "KK TTTTTT KK SSSSS", - level=level, lang="en") + "if answer is 'yes'", "KK TTTTTT KK SSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_is3(self, level): self.assert_highlighted_chr( - "if answer is 4242", - "KK TTTTTT KK NNNN", - level=level, lang="en") + "if answer is 4242", "KK TTTTTT KK NNNN", level=level, lang="en" + ) - @parameterized.expand([ - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_eq1(self, level): self.assert_highlighted_chr( - "if answer == yes", - "KK TTTTTT KK TTT", - level=level, lang="en") + "if answer == yes", "KK TTTTTT KK TTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_eq2(self, level): self.assert_highlighted_chr( - "if answer == 'yes'", - "KK TTTTTT KK SSSSS", - level=level, lang="en") + "if answer == 'yes'", "KK TTTTTT KK SSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_eq3(self, level): self.assert_highlighted_chr( - "if answer == 4242", - "KK TTTTTT KK NNNN", - level=level, lang="en") + "if answer == 4242", "KK TTTTTT KK NNNN", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_diff1(self, level): self.assert_highlighted_chr( - "if answer != yes", - "KK TTTTTT KK TTT", - level=level, lang="en") + "if answer != yes", "KK TTTTTT KK TTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_diff2(self, level): self.assert_highlighted_chr( - "if answer != 'yes'", - "KK TTTTTT KK SSSSS", - level=level, lang="en") + "if answer != 'yes'", "KK TTTTTT KK SSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_diff3(self, level): self.assert_highlighted_chr( - "if answer != 4242", - "KK TTTTTT KK NNNN", - level=level, lang="en") + "if answer != 4242", "KK TTTTTT KK NNNN", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_sup1(self, level): self.assert_highlighted_chr( - "if answer <= yes", - "KK TTTTTT KK TTT", - level=level, lang="en") + "if answer <= yes", "KK TTTTTT KK TTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_sup2(self, level): self.assert_highlighted_chr( - "if answer <= 'yes'", - "KK TTTTTT KK SSSSS", - level=level, lang="en") + "if answer <= 'yes'", "KK TTTTTT KK SSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_sup3(self, level): self.assert_highlighted_chr( - "if answer <= 4242", - "KK TTTTTT KK NNNN", - level=level, lang="en") + "if answer <= 4242", "KK TTTTTT KK NNNN", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_inf1(self, level): self.assert_highlighted_chr( - "if answer >= yes", - "KK TTTTTT KK TTT", - level=level, lang="en") + "if answer >= yes", "KK TTTTTT KK TTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_inf2(self, level): self.assert_highlighted_chr( - "if answer >= 'yes'", - "KK TTTTTT KK SSSSS", - level=level, lang="en") + "if answer >= 'yes'", "KK TTTTTT KK SSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_inf3(self, level): self.assert_highlighted_chr( - "if answer >= 4242", - "KK TTTTTT KK NNNN", - level=level, lang="en") + "if answer >= 4242", "KK TTTTTT KK NNNN", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_in1(self, level): self.assert_highlighted_chr( - "if answer in yes", - "KK TTTTTT KK TTT", - level=level, lang="en") + "if answer in yes", "KK TTTTTT KK TTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_in2(self, level): self.assert_highlighted_chr( - "if answer in 'yes'", - "KK TTTTTT KK SSSSS", - level=level, lang="en") + "if answer in 'yes'", "KK TTTTTT KK SSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_in3(self, level): self.assert_highlighted_chr( - "if answer in 4242", - "KK TTTTTT KK NNNN", - level=level, lang="en") + "if answer in 4242", "KK TTTTTT KK NNNN", level=level, lang="en" + ) - @parameterized.expand([ - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_else(self, level): - self.assert_highlighted_chr( - "else", - "KKKK", - level=level, lang="en") + self.assert_highlighted_chr("else", "KKKK", level=level, lang="en") - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_is1_or(self, level): self.assert_highlighted_chr( "if answer is var or answer is 'yes'", "KK TTTTTT KK TTT KK TTTTTT KK SSSSS", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_is2_and(self, level): self.assert_highlighted_chr( "if answer is 246 and answer is 'yes'", "KK TTTTTT KK NNN KKK TTTTTT KK SSSSS", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_eq1_or(self, level): self.assert_highlighted_chr( "if answer == var or answer != 'yes'", "KK TTTTTT KK TTT KK TTTTTT KK SSSSS", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_eq2_and(self, level): self.assert_highlighted_chr( "if answer != 246 and answer == 'yes'", "KK TTTTTT KK NNN KKK TTTTTT KK SSSSS", - level=level, lang="en") + level=level, + lang="en", + ) diff --git a/tests/test_highlighting/test_if_inline.py b/tests/test_highlighting/test_if_inline.py index f6e9f9605b7..1722f943934 100644 --- a/tests/test_highlighting/test_if_inline.py +++ b/tests/test_highlighting/test_if_inline.py @@ -4,93 +4,120 @@ class HighlighterTestIfInline(HighlightTester): - - @parameterized.expand([ - ("level5"), - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level5"), + ("level6"), + ("level7"), + ] + ) def test_if_print(self, level): self.assert_highlighted_chr( "if cat is chat print 'Terrific!' var at random", "KK TTT KK TTTT KKKKK SSSSSSSSSSS TTT KK KKKKKK", - level=level, lang="en") + level=level, + lang="en", + ) def test_if_affectation1(self): self.assert_highlighted_chr( "if cat is 666 price is 5", "KK TTT KK TTT TTTTT KK T", - level="level5", lang="en") + level="level5", + lang="en", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ] + ) def test_if_affectation2(self, level): self.assert_highlighted_chr( "if cat is 666 price is 5", "KK TTT KK NNN TTTTT KK N", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ] + ) def test_if_affectation3(self, level): self.assert_highlighted_chr( "if cat is 666 price = 5 + 42", "KK TTT KK NNN TTTTT K N K NN", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level5"), - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level5"), + ("level6"), + ("level7"), + ] + ) def test_if_else_print(self, level): self.assert_highlighted_chr( "if anything is no print 'Thats it!' goodanswer at random else print 'One ' anything at random", "KK TTTTTTTT KK TT KKKKK SSSSSSSSSSS TTTTTTTTTT KK KKKKKK KKKK KKKKK SSSSSS TTTTTTTT KK KKKKKK", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level5"), - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level5"), + ("level6"), + ("level7"), + ] + ) def test_if_else_is(self, level): self.assert_highlighted_chr( "if anything is no var is test else var is azerty", "KK TTTTTTTT KK TT TTT KK TTTT KKKK TTT KK TTTTTT", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level5"), - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level5"), + ("level6"), + ("level7"), + ] + ) def test_else_print(self, level): self.assert_highlighted_chr( "else print 'Oh no! You are being eaten by a...' monsters at random", "KKKK KKKKK SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS TTTTTTTT KK KKKKKK", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ] + ) def test_else_math(self, level): self.assert_highlighted_chr( - "else price = 5 + 42", - "KKKK TTTTT K N K NN", - level=level, lang="en") + "else price = 5 + 42", "KKKK TTTTT K N K NN", level=level, lang="en" + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ] + ) def test_else_is(self, level): self.assert_highlighted_chr( - "else price is 5", - "KKKK TTTTT KK N", - level=level, lang="en") + "else price is 5", "KKKK TTTTT KK N", level=level, lang="en" + ) diff --git a/tests/test_highlighting/test_if_pressed.py b/tests/test_highlighting/test_if_pressed.py index 81966e9d109..033b364e4d2 100644 --- a/tests/test_highlighting/test_if_pressed.py +++ b/tests/test_highlighting/test_if_pressed.py @@ -4,90 +4,105 @@ class HighlighterTestIfPressed(HighlightTester): - - @parameterized.expand([ - ("level5"), - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level5"), + ("level6"), + ("level7"), + ] + ) def test_if_is_pressed(self, level): self.assert_highlighted_chr( - "if x is pressed", - "KK T KK EEEEEEE", - level=level, lang="en") + "if x is pressed", "KK T KK EEEEEEE", level=level, lang="en" + ) - @parameterized.expand([ - ("level5"), - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level5"), + ("level6"), + ("level7"), + ] + ) def test_if_is_pressed_print(self, level): self.assert_highlighted_chr( "if x is pressed print 'Yay!'", "KK T KK EEEEEEE KKKKK SSSSSS", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level5"), - ("level6"), - ("level7"), - ]) + @parameterized.expand( + [ + ("level5"), + ("level6"), + ("level7"), + ] + ) def test_if_is_pressed_print_else_print(self, level): self.assert_highlighted_chr( "if x is pressed print 'Yay!' else print 'Boo!'", "KK T KK EEEEEEE KKKKK SSSSSS KKKK KKKKK SSSSSS", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_is_pressed1(self, level): self.assert_highlighted_chr( - "if x is pressed", - "KK T KK EEEEEEE", - level=level, lang="en") + "if x is pressed", "KK T KK EEEEEEE", level=level, lang="en" + ) - @parameterized.expand([ - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_is_pressed_print1(self, level): self.assert_highlighted_chr_multi_line( "if x is pressed", "KK T KK EEEEEEE", " print 'Yay!'", " KKKKK SSSSSS", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_if_is_pressed_print_else_print1(self, level): self.assert_highlighted_chr_multi_line( "if x is pressed", @@ -98,4 +113,6 @@ def test_if_is_pressed_print_else_print1(self, level): "KKKK", " print 'Boo!'", " KKKKK SSSSSS", - level=level, lang="en") + level=level, + lang="en", + ) diff --git a/tests/test_highlighting/test_input.py b/tests/test_highlighting/test_input.py index b19355e8398..d30627d86bd 100644 --- a/tests/test_highlighting/test_input.py +++ b/tests/test_highlighting/test_input.py @@ -4,55 +4,57 @@ class HighlighterTestInput(HighlightTester): - - @parameterized.expand([ - ("level2"), - ("level3"), - ]) + @parameterized.expand( + [ + ("level2"), + ("level3"), + ] + ) def test_is(self, level): self.assert_highlighted_chr( - "sword is ask l ost", - "TTTTT KK KKK T TTT", - level=level, lang="en") + "sword is ask l ost", "TTTTT KK KKK T TTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_is_string(self, level): self.assert_highlighted_chr( - "sword is ask 'lo R st'", - "TTTTT KK KKK SSSSSSSSS", - level=level, lang="en") + "sword is ask 'lo R st'", "TTTTT KK KKK SSSSSSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_is_string_equal(self, level): self.assert_highlighted_chr( - "sword = ask 'lo st'", - "TTTTT K KKK SSSSSSS", - level=level, lang="en") + "sword = ask 'lo st'", "TTTTT K KKK SSSSSSS", level=level, lang="en" + ) diff --git a/tests/test_highlighting/test_list.py b/tests/test_highlighting/test_list.py index 6f1763ca0e8..0c67a6831aa 100644 --- a/tests/test_highlighting/test_list.py +++ b/tests/test_highlighting/test_list.py @@ -4,206 +4,244 @@ class HighlighterTestList(HighlightTester): - - @parameterized.expand([ - ("level3"), - ("level4"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ] + ) def test_list(self, level): self.assert_highlighted_chr( "sword is l ost, 12, aver i", "TTTTT KK TTTTTK TTK TTTTTT", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_list_string(self, level): self.assert_highlighted_chr( "sword is 'l ost', '12', 'aver i'", "TTTTT KK SSSSSSSK SSSSK SSSSSSSS", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level4"), - ]) + @parameterized.expand( + [ + ("level4"), + ] + ) def test_list_string_unquote(self, level): self.assert_highlighted_chr( "sword is 'l ost', '12', 'aver i'", "TTTTT KK TTTTTTTK TTTTK TTTTTTTT", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ]) + @parameterized.expand( + [ + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ] + ) def test_list_string_smiley(self, level): self.assert_highlighted_chr( - "variable is 🦔, 🦉, 🐿, 🦇", - "TTTTTTTT KK TK TK TK T", - level=level, lang="en") + "variable is 🦔, 🦉, 🐿, 🦇", "TTTTTTTT KK TK TK TK T", level=level, lang="en" + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_list_string_equ(self, level): self.assert_highlighted_chr( "sword = 'lost', '1 2', 'ave ri'", "TTTTT K SSSSSSK SSSSSK SSSSSSSS", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ] + ) def test_list_string_equ_smiley(self, level): self.assert_highlighted_chr( - "variable = 🦔, 🦉, 🐿, 🦇", - "TTTTTTTT K TK TK TK T", - level=level, lang="en") + "variable = 🦔, 🦉, 🐿, 🦇", "TTTTTTTT K TK TK TK T", level=level, lang="en" + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_list_number(self, level): self.assert_highlighted_chr( "sword is 'l ost', 12, 'aver i', 1 , 3, 3", "TTTTT KK SSSSSSSK NNK SSSSSSSSK N K NK N", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_list_number_equ(self, level): self.assert_highlighted_chr( "sword = 'l ost', 12, 'aver i', 1 , 3, 3", "TTTTT K SSSSSSSK NNK SSSSSSSSK N K NK N", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_list_number_float(self, level): self.assert_highlighted_chr( "sword is 'l ost', 12, 'aver i', 1.97 , 3.5, 3", "TTTTT KK SSSSSSSK NNK SSSSSSSSK NNNN K NNNK N", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_list_number_equ_float(self, level): self.assert_highlighted_chr( "sword = 'l ost', 12, 'aver i', 1.97 , 3.5, 3", "TTTTT K SSSSSSSK NNK SSSSSSSSK NNNN K NNNK N", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_list_add(self, level): self.assert_highlighted_chr( - "add penguin to animals", - "KKK TTTTTTT KK TTTTTTT", - level=level, lang="en") + "add penguin to animals", "KKK TTTTTTT KK TTTTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_list_remove(self, level): self.assert_highlighted_chr( "remove allergies from flavors", "KKKKKK TTTTTTTTT KKKK TTTTTTT", - level=level, lang="en") + level=level, + lang="en", + ) diff --git a/tests/test_highlighting/test_numbers.py b/tests/test_highlighting/test_numbers.py index 11a8fc74c35..81789f63f52 100644 --- a/tests/test_highlighting/test_numbers.py +++ b/tests/test_highlighting/test_numbers.py @@ -4,221 +4,233 @@ class HighlighterTestNumbers(HighlightTester): - - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number(self, level): self.assert_highlighted_chr( - "sword is 12346", - "TTTTT KK NNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword is 12346", "TTTTT KK NNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_equal(self, level): self.assert_highlighted_chr( - "sword = 12346", - "TTTTT K NNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword = 12346", "TTTTT K NNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_float(self, level): self.assert_highlighted_chr( - "sword = 123.46", - "TTTTT K NNNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword = 123.46", "TTTTT K NNNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_equal_float(self, level): self.assert_highlighted_chr( - "sword is 123.46", - "TTTTT KK NNNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ]) + "sword is 123.46", "TTTTT KK NNNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ] + ) def test_nb_is_number_equal_float_unknow(self, level): self.assert_highlighted_chr( - "sword = 123.46", - "TTTTT K NNNTTT", - level=level, lang="en") - - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword = 123.46", "TTTTT K NNNTTT", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_equal_plus(self, level): self.assert_highlighted_chr( - "sword = 12346 + 78564", - "TTTTT K NNNNN K NNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword = 12346 + 78564", "TTTTT K NNNNN K NNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_equal_float_plus(self, level): self.assert_highlighted_chr( - "sword = 123.46 + 7949.8", - "TTTTT K NNNNNN K NNNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword = 123.46 + 7949.8", "TTTTT K NNNNNN K NNNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_equal_minus(self, level): self.assert_highlighted_chr( - "sword = 12346 - 78564", - "TTTTT K NNNNN K NNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword = 12346 - 78564", "TTTTT K NNNNN K NNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_equal_float_minus(self, level): self.assert_highlighted_chr( - "sword = 123.46 - 7949.8", - "TTTTT K NNNNNN K NNNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword = 123.46 - 7949.8", "TTTTT K NNNNNN K NNNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_equal_multi(self, level): self.assert_highlighted_chr( - "sword = 12346 * 78564", - "TTTTT K NNNNN K NNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword = 12346 * 78564", "TTTTT K NNNNN K NNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_equal_float_multi(self, level): self.assert_highlighted_chr( - "sword = 123.46 * 7949.8", - "TTTTT K NNNNNN K NNNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword = 123.46 * 7949.8", "TTTTT K NNNNNN K NNNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_equal_div(self, level): self.assert_highlighted_chr( - "sword = 12346 / 78564", - "TTTTT K NNNNN K NNNNN", - level=level, lang="en") - - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + "sword = 12346 / 78564", "TTTTT K NNNNN K NNNNN", level=level, lang="en" + ) + + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_nb_is_number_equal_float_div(self, level): self.assert_highlighted_chr( - "sword = 123.46 / 7949.8", - "TTTTT K NNNNNN K NNNNNN", - level=level, lang="en") + "sword = 123.46 / 7949.8", "TTTTT K NNNNNN K NNNNNN", level=level, lang="en" + ) diff --git a/tests/test_highlighting/test_over_coloration.py b/tests/test_highlighting/test_over_coloration.py index 0d554abbbd5..ade0cbc6847 100644 --- a/tests/test_highlighting/test_over_coloration.py +++ b/tests/test_highlighting/test_over_coloration.py @@ -4,171 +4,188 @@ class HighlighterTestOverCol(HighlightTester): - - @parameterized.expand([ - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ]) + @parameterized.expand( + [ + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ] + ) def test_is1(self, level): self.assert_highlighted_chr( - "ask is lost", - "TTT KK TTTT", - level=level, lang="en") + "ask is lost", "TTT KK TTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_is2(self, level): self.assert_highlighted_chr( - "ask is 'lost'", - "TTT KK SSSSSS", - level=level, lang="en") + "ask is 'lost'", "TTT KK SSSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ] + ) def test_Eq1(self, level): - self.assert_highlighted_chr( - "ask = lost", - "TTT K TTTT", - level=level, lang="en") + self.assert_highlighted_chr("ask = lost", "TTT K TTTT", level=level, lang="en") - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_Eq2(self, level): self.assert_highlighted_chr( - "ask = 'lost'", - "TTT K SSSSSS", - level=level, lang="en") + "ask = 'lost'", "TTT K SSSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level2"), - ("level3"), - ]) + @parameterized.expand( + [ + ("level2"), + ("level3"), + ] + ) def test_print1(self, level): self.assert_highlighted_chr( - "print hello world! ask", - "KKKKK TTTTTTTTTTTTTTTT", - level=level, lang="en") + "print hello world! ask", "KKKKK TTTTTTTTTTTTTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print2(self, level): self.assert_highlighted_chr( "print 'hello world!' ask", "KKKKK SSSSSSSSSSSSSS TTT", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_at_prefix(self, level): self.assert_highlighted_chr( - "print atoptis at random", - "KKKKK TTTTTTT KK KKKKKK", - level=level, lang='en') + "print atoptis at random", "KKKKK TTTTTTT KK KKKKKK", level=level, lang="en" + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_at_prefix_nl(self, level): self.assert_highlighted_chr( - "print optis at random", - "KKKKK TTTTT KK KKKKKK", - level=level, lang='nl') + "print optis at random", "KKKKK TTTTT KK KKKKKK", level=level, lang="nl" + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ] + ) def test_from_prefix(self, level): self.assert_highlighted_chr( "remove fromyour_name from people", "KKKKKK TTTTTTTTTTTTT KKKK TTTTTT", - level=level, lang='en') + level=level, + lang="en", + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ] + ) def test_from_prefix_nl(self, level): self.assert_highlighted_chr( "remove uitfromyour_name from people", "KKKKKK TTTTTTTTTTTTTTTT KKKK TTTTTT", - level=level, lang='nl') + level=level, + lang="nl", + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ] + ) def test_to_prefix(self, level): self.assert_highlighted_chr( - "add toanimal to animals", - "KKK TTTTTTTT KK TTTTTTT", - level=level, lang='en') + "add toanimal to animals", "KKK TTTTTTTT KK TTTTTTT", level=level, lang="en" + ) diff --git a/tests/test_highlighting/test_print.py b/tests/test_highlighting/test_print.py index 64122b3aeb4..9b14c718eb8 100644 --- a/tests/test_highlighting/test_print.py +++ b/tests/test_highlighting/test_print.py @@ -4,403 +4,475 @@ class HighlighterTestPrint(HighlightTester): - - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ] + ) def test_print1(self, level): self.assert_highlighted_chr( - "print hello world!", - "KKKKK TTTTTTTTTTTT", - level=level, lang='en') + "print hello world!", "KKKKK TTTTTTTTTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print2(self, level): self.assert_highlighted_chr( - "print 'hello world!'", - "KKKKK SSSSSSSSSSSSSS", - level=level, lang='en') + "print 'hello world!'", "KKKKK SSSSSSSSSSSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print3(self, level): self.assert_highlighted_chr( - 'print "hello world!"', - "KKKKK SSSSSSSSSSSSSS", - level=level, lang='en') + 'print "hello world!"', "KKKKK SSSSSSSSSSSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print4(self, level): self.assert_highlighted_chr( - "print «hello world!»", - "KKKKK SSSSSSSSSSSSSS", - level=level, lang='en') + "print «hello world!»", "KKKKK SSSSSSSSSSSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_incomplete1(self, level): self.assert_highlighted_chr( "print 'hello world!' var at random", "KKKKK SSSSSSSSSSSSSS TTT KK KKKKKK", - level=level, lang='en') + level=level, + lang="en", + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_incomplete2(self, level): self.assert_highlighted_chr( "print 'hello world! var at random", "KKKKK TTTTTTSTTTTTTSTTTSTTSTTTTTT", - level=level, lang='en') + level=level, + lang="en", + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_incomplete3(self, level): self.assert_highlighted_chr( 'print "hello world! var at random', "KKKKK TTTTTTSTTTTTTSTTTSTTSTTTTTT", - level=level, lang='en') + level=level, + lang="en", + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_incomplete4(self, level): self.assert_highlighted_chr( "print «hello world! var at random", "KKKKK TTTTTTSTTTTTTSTTTSTTSTTTTTT", - level=level, lang='en') + level=level, + lang="en", + ) - @parameterized.expand([ - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_random_alone(self, level): self.assert_highlighted_chr( - "print animals at random", - "KKKKK TTTTTTT KK KKKKKK", - level=level, lang='en') + "print animals at random", "KKKKK TTTTTTT KK KKKKKK", level=level, lang="en" + ) def test_print_random1(self): self.assert_highlighted_chr( "print people at random does the dishes", "KKKKK TTTTTT KK KKKKKK TTTTTTTTTTTTTTT", - level="level3", lang='en') + level="level3", + lang="en", + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_random2(self, level): self.assert_highlighted_chr( "print 'The' people at random 'does the dishes'", "KKKKK SSSSS TTTTTT KK KKKKKK SSSSSSSSSSSSSSSSS", - level=level, lang='en') + level=level, + lang="en", + ) def test_print_at1(self): self.assert_highlighted_chr( "print people at 3 does the dishes", "KKKKK TTTTTT KK T TTTTTTTTTTTTTTT", - level="level3", lang='en') + level="level3", + lang="en", + ) - @parameterized.expand([ - ("level4"), - ("level5"), - ]) + @parameterized.expand( + [ + ("level4"), + ("level5"), + ] + ) def test_print_at2(self, level): self.assert_highlighted_chr( "print 'The' people at 3 'does the dishes'", "KKKKK SSSSS TTTTTT KK T SSSSSSSSSSSSSSSSS", - level=level, lang='en') + level=level, + lang="en", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_at3(self, level): self.assert_highlighted_chr( "print 'The' people at 3 'does the dishes'", "KKKKK SSSSS TTTTTT KK N SSSSSSSSSSSSSSSSS", - level=level, lang='en') + level=level, + lang="en", + ) def test_print_18_1(self): self.assert_highlighted_chr( "print ('Great job!!!')", "KKKKK KSSSSSSSSSSSSSSK", - level="level18", lang='en') + level="level18", + lang="en", + ) def test_print_18_2(self): self.assert_highlighted_chr( "print('my name is ', name, 'my name is ')", "KKKKKKSSSSSSSSSSSSSK TTTTK SSSSSSSSSSSSSK", - level="level18", lang='en') + level="level18", + lang="en", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_number_equal_plus(self, level): self.assert_highlighted_chr( "print 'answer is' 12346 + 78564", "KKKKK SSSSSSSSSSS NNNNN K NNNNN", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_number_equal_float_plus(self, level): self.assert_highlighted_chr( "print 'answer is' 123.46 + 7949.8", "KKKKK SSSSSSSSSSS NNNNNN K NNNNNN", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_number_equal_minus(self, level): self.assert_highlighted_chr( "print 'answer is' 12346 - 78564", "KKKKK SSSSSSSSSSS NNNNN K NNNNN", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_number_equal_float_minus(self, level): self.assert_highlighted_chr( "print 'answer is' 123.46 - 7949.8", "KKKKK SSSSSSSSSSS NNNNNN K NNNNNN", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_number_equal_multi(self, level): self.assert_highlighted_chr( "print 'answer is' 12346 * 78564", "KKKKK SSSSSSSSSSS NNNNN K NNNNN", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_number_equal_float_multi(self, level): self.assert_highlighted_chr( "print 'answer is' 123.46 * 7949.8", "KKKKK SSSSSSSSSSS NNNNNN K NNNNNN", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_number_equal_div(self, level): self.assert_highlighted_chr( "print 'answer is' 12346 / 78564", "KKKKK SSSSSSSSSSS NNNNN K NNNNN", - level=level, lang="en") + level=level, + lang="en", + ) - @parameterized.expand([ - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_print_number_equal_float_div(self, level): self.assert_highlighted_chr( "print 'answer is' 123.46 / 7949.8", "KKKKK SSSSSSSSSSS NNNNNN K NNNNNN", - level=level, lang="en") + level=level, + lang="en", + ) diff --git a/tests/test_highlighting/test_repeat.py b/tests/test_highlighting/test_repeat.py index 7f1a0c98fc9..85c1648fdb9 100644 --- a/tests/test_highlighting/test_repeat.py +++ b/tests/test_highlighting/test_repeat.py @@ -4,81 +4,90 @@ class HighlighterTestRepeat(HighlightTester): - - @parameterized.expand([ - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_repeat_print(self, lang): self.assert_highlighted_chr( "repeat 3 times print 'Hedy is fun!'", - 'KKKKKK N KKKKK KKKKK SSSSSSSSSSSSSS', - level=lang, lang="en") + "KKKKKK N KKKKK KKKKK SSSSSSSSSSSSSS", + level=lang, + lang="en", + ) - @parameterized.expand([ - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ]) + @parameterized.expand( + [ + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ] + ) def test_repeat_ask(self, lang): self.assert_highlighted_chr( "repeat 3 times question is ask 'What do you want to know?'", - 'KKKKKK N KKKKK TTTTTTTT KK KKK SSSSSSSSSSSSSSSSSSSSSSSSSSS', - level=lang, lang="en") + "KKKKKK N KKKKK TTTTTTTT KK KKK SSSSSSSSSSSSSSSSSSSSSSSSSSS", + level=lang, + lang="en", + ) - @parameterized.expand([ - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_repeat_alone(self, lang): self.assert_highlighted_chr( - "repeat people times", - 'KKKKKK TTTTTT KKKKK', - level=lang, lang="en") + "repeat people times", "KKKKKK TTTTTT KKKKK", level=lang, lang="en" + ) - @parameterized.expand([ - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_repeat_number(self, lang): self.assert_highlighted_chr( - "repeat 99 times", - 'KKKKKK NN KKKKK', - level=lang, lang="en") + "repeat 99 times", "KKKKKK NN KKKKK", level=lang, lang="en" + ) diff --git a/tests/test_highlighting/test_sleep.py b/tests/test_highlighting/test_sleep.py index c198581199b..979749fb36d 100644 --- a/tests/test_highlighting/test_sleep.py +++ b/tests/test_highlighting/test_sleep.py @@ -4,81 +4,76 @@ class HighlighterTestSleep(HighlightTester): - - @parameterized.expand([ - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_sleep_alone(self, level): - self.assert_highlighted_chr( - "sleep", - "KKKKK", - level=level, lang="en") + self.assert_highlighted_chr("sleep", "KKKKK", level=level, lang="en") - @parameterized.expand([ - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ]) + @parameterized.expand( + [ + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ] + ) def test_sleep(self, level): - self.assert_highlighted_chr( - "sleep 12", - "KKKKK TT", - level=level, lang="en") + self.assert_highlighted_chr("sleep 12", "KKKKK TT", level=level, lang="en") - @parameterized.expand([ - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_sleep_number(self, level): - self.assert_highlighted_chr( - "sleep 12", - "KKKKK NN", - level=level, lang="en") + self.assert_highlighted_chr("sleep 12", "KKKKK NN", level=level, lang="en") - @parameterized.expand([ - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ("level11"), - ("level12"), - ("level13"), - ("level14"), - ("level15"), - ("level16"), - ("level17"), - ]) + @parameterized.expand( + [ + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ("level11"), + ("level12"), + ("level13"), + ("level14"), + ("level15"), + ("level16"), + ("level17"), + ] + ) def test_sleep_var(self, level): - self.assert_highlighted_chr( - "sleep var", - "KKKKK TTT", - level=level, lang="en") + self.assert_highlighted_chr("sleep var", "KKKKK TTT", level=level, lang="en") diff --git a/tests/test_highlighting/test_turtle.py b/tests/test_highlighting/test_turtle.py index f14647de329..dead2871d4d 100644 --- a/tests/test_highlighting/test_turtle.py +++ b/tests/test_highlighting/test_turtle.py @@ -4,293 +4,285 @@ class HighlighterTestTurtle(HighlightTester): - - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ] + ) def test_forward(self, level): - self.assert_highlighted_chr( - "forward 25", - "KKKKKKK TT", - level=level, lang="en") + self.assert_highlighted_chr("forward 25", "KKKKKKK TT", level=level, lang="en") - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ] + ) def test_forward_number(self, level): - self.assert_highlighted_chr( - "forward 25", - "KKKKKKK NN", - level=level, lang="en") + self.assert_highlighted_chr("forward 25", "KKKKKKK NN", level=level, lang="en") - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ] + ) def test_forward_alone(self, level): - self.assert_highlighted_chr( - "forward", - "KKKKKKK", - level=level, lang="en") + self.assert_highlighted_chr("forward", "KKKKKKK", level=level, lang="en") def test_turn_left(self): - self.assert_highlighted_chr( - "turn left", - "KKKK TTTT", - level="level1", lang="en") + self.assert_highlighted_chr("turn left", "KKKK TTTT", level="level1", lang="en") def test_turn_right(self): self.assert_highlighted_chr( - "turn right", - "KKKK TTTTT", - level="level1", lang="en") + "turn right", "KKKK TTTTT", level="level1", lang="en" + ) - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ] + ) def test_turn_alone(self, level): - self.assert_highlighted_chr( - "turn", - "KKKK", - level=level, lang="en") + self.assert_highlighted_chr("turn", "KKKK", level=level, lang="en") - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ] + ) def test_turn(self, level): - self.assert_highlighted_chr( - "turn 25", - "KKKK TT", - level=level, lang="en") + self.assert_highlighted_chr("turn 25", "KKKK TT", level=level, lang="en") - @parameterized.expand([ - ("level6"), - ("level7"), - ("level8"), - ]) + @parameterized.expand( + [ + ("level6"), + ("level7"), + ("level8"), + ] + ) def test_turn_number(self, level): - self.assert_highlighted_chr( - "turn 25", - "KKKK NN", - level=level, lang="en") + self.assert_highlighted_chr("turn 25", "KKKK NN", level=level, lang="en") - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_black(self, level): self.assert_highlighted_chr( - "color black", - "KKKKK TTTTT", - level=level, lang="en") + "color black", "KKKKK TTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_blue(self, level): - self.assert_highlighted_chr( - "color blue", - "KKKKK TTTT", - level=level, lang="en") + self.assert_highlighted_chr("color blue", "KKKKK TTTT", level=level, lang="en") - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_brown(self, level): self.assert_highlighted_chr( - "color brown", - "KKKKK TTTTT", - level=level, lang="en") + "color brown", "KKKKK TTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_gray(self, level): - self.assert_highlighted_chr( - "color gray", - "KKKKK TTTT", - level=level, lang="en") + self.assert_highlighted_chr("color gray", "KKKKK TTTT", level=level, lang="en") - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_green(self, level): self.assert_highlighted_chr( - "color green", - "KKKKK TTTTT", - level=level, lang="en") + "color green", "KKKKK TTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_orange(self, level): self.assert_highlighted_chr( - "color orange", - "KKKKK TTTTTT", - level=level, lang="en") + "color orange", "KKKKK TTTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_pink(self, level): - self.assert_highlighted_chr( - "color pink", - "KKKKK TTTT", - level=level, lang="en") + self.assert_highlighted_chr("color pink", "KKKKK TTTT", level=level, lang="en") - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_purple(self, level): self.assert_highlighted_chr( - "color purple", - "KKKKK TTTTTT", - level=level, lang="en") + "color purple", "KKKKK TTTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_red(self, level): - self.assert_highlighted_chr( - "color red", - "KKKKK TTT", - level=level, lang="en") + self.assert_highlighted_chr("color red", "KKKKK TTT", level=level, lang="en") - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_white(self, level): self.assert_highlighted_chr( - "color white", - "KKKKK TTTTT", - level=level, lang="en") + "color white", "KKKKK TTTTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level1"), - ("level2"), - ("level3"), - ("level4"), - ("level5"), - ("level6"), - ("level7"), - ("level8"), - ("level9"), - ("level10"), - ]) + @parameterized.expand( + [ + ("level1"), + ("level2"), + ("level3"), + ("level4"), + ("level5"), + ("level6"), + ("level7"), + ("level8"), + ("level9"), + ("level10"), + ] + ) def test_color_yellow(self, level): self.assert_highlighted_chr( - "color yellow", - "KKKKK TTTTTT", - level=level, lang="en") + "color yellow", "KKKKK TTTTTT", level=level, lang="en" + ) diff --git a/tests/test_highlighting/test_while.py b/tests/test_highlighting/test_while.py index a163f9f0d3e..adc6631f748 100644 --- a/tests/test_highlighting/test_while.py +++ b/tests/test_highlighting/test_while.py @@ -4,75 +4,80 @@ class HighlighterTestWhile(HighlightTester): - - @parameterized.expand([ - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_while_str(self, level): self.assert_highlighted_chr( - "while keys == 'lost'", - "KKKKK TTTT KK SSSSSS", - level=level, lang="en") + "while keys == 'lost'", "KKKKK TTTT KK SSSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_while_diff_str(self, level): self.assert_highlighted_chr( - "while keys != 'lost'", - "KKKKK TTTT KK SSSSSS", - level=level, lang="en") + "while keys != 'lost'", "KKKKK TTTT KK SSSSSS", level=level, lang="en" + ) - @parameterized.expand([ - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_while_number(self, level): self.assert_highlighted_chr( - "while keys == 123", - "KKKKK TTTT KK NNN", - level=level, lang="en") + "while keys == 123", "KKKKK TTTT KK NNN", level=level, lang="en" + ) - @parameterized.expand([ - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_while_diff_number(self, level): self.assert_highlighted_chr( - "while keys != 123", - "KKKKK TTTT KK NNN", - level=level, lang="en") + "while keys != 123", "KKKKK TTTT KK NNN", level=level, lang="en" + ) - @parameterized.expand([ - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_while_var(self, level): self.assert_highlighted_chr( - "while keys == var", - "KKKKK TTTT KK TTT", - level=level, lang="en") + "while keys == var", "KKKKK TTTT KK TTT", level=level, lang="en" + ) - @parameterized.expand([ - ("level15"), - ("level16"), - ("level17"), - ("level18"), - ]) + @parameterized.expand( + [ + ("level15"), + ("level16"), + ("level17"), + ("level18"), + ] + ) def test_while_diff_var(self, level): self.assert_highlighted_chr( - "while keys != var", - "KKKKK TTTT KK TTT", - level=level, lang="en") + "while keys != var", "KKKKK TTTT KK TTT", level=level, lang="en" + ) diff --git a/tests/test_level/test_level_01.py b/tests/test_level/test_level_01.py index 070d58a3746..7b826fbee22 100644 --- a/tests/test_level/test_level_01.py +++ b/tests/test_level/test_level_01.py @@ -10,7 +10,7 @@ class TestsLevel1(HedyTester): level = 1 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -22,7 +22,7 @@ class TestsLevel1(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # print tests @@ -30,28 +30,28 @@ class TestsLevel1(HedyTester): def test_print(self): code = "print Hallo welkom bij Hedy!" expected = "print('Hallo welkom bij Hedy!')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" expected_commands = [Command.print] self.single_level_tester( code=code, expected=expected, output=output, - expected_commands=expected_commands + expected_commands=expected_commands, ) self.assertEqual([output], hedy.all_print_arguments(code, self.level)) def test_print_no_space(self): code = "printHallo welkom bij Hedy!" expected = "print('Hallo welkom bij Hedy!')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" expected_commands = [Command.print] self.single_level_tester( code=code, expected=expected, output=output, - expected_commands=expected_commands + expected_commands=expected_commands, ) self.assertEqual([output], hedy.all_print_arguments(code, self.level)) @@ -60,30 +60,43 @@ def test_print_line_with_spaces_works(self): expected = "print('hallo')\n\nprint('hallo')" expected_commands = [Command.print, Command.print] - self.single_level_tester(code=code, expected=expected, expected_commands=expected_commands) + self.single_level_tester( + code=code, expected=expected, expected_commands=expected_commands + ) def test_print_comma(self): code = "print one, two, three" expected = "print('one, two, three')" expected_commands = [Command.print] - self.single_level_tester(code=code, expected=expected, expected_commands=expected_commands) + self.single_level_tester( + code=code, expected=expected, expected_commands=expected_commands + ) def test_print_multiple_lines(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print Hallo welkom bij Hedy - print Mooi hoor""") + print Mooi hoor""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ print('Hallo welkom bij Hedy') - print('Mooi hoor')""") + print('Mooi hoor')""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ Hallo welkom bij Hedy - Mooi hoor""") + Mooi hoor""" + ) self.single_level_tester(code=code, expected=expected, output=output) - self.assertEqual(['Hallo welkom bij Hedy', 'Mooi hoor'], hedy.all_print_arguments(code, self.level)) + self.assertEqual( + ["Hallo welkom bij Hedy", "Mooi hoor"], + hedy.all_print_arguments(code, self.level), + ) def test_print_single_quoted_text(self): code = "print 'Welcome to OceanView!'" @@ -141,28 +154,29 @@ def test_print_with_spaces(self): def test_print_nl(self): code = "print Hallo welkom bij Hedy!" expected = "print('Hallo welkom bij Hedy!')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" - self.single_level_tester(code=code, expected=expected, output=output, lang='nl') + self.single_level_tester(code=code, expected=expected, output=output, lang="nl") def test_print_ar(self): code = "قول أهلا ومرحبا بكم في هيدي!" expected = "print('أهلا ومرحبا بكم في هيدي!')" - output = 'أهلا ومرحبا بكم في هيدي!' + output = "أهلا ومرحبا بكم في هيدي!" - self.single_level_tester(code=code, expected=expected, output=output, lang='ar') + self.single_level_tester(code=code, expected=expected, output=output, lang="ar") def test_print_ar_tatweel_all_places(self): code = "ـــقــولـ أ" expected = "print('أ')" - output = 'أ' + output = "أ" self.single_level_tester( code=code, expected=expected, output=output, translate=False, # translation will remove the tatweels, we will deal with that later - lang='ar') + lang="ar", + ) def test_ask_ar_tatweel_all_places(self): code = "اســأل أ" @@ -172,7 +186,8 @@ def test_ask_ar_tatweel_all_places(self): code=code, expected=expected, translate=False, # translation will remove the tatweels, we will deal with that later - lang='ar') + lang="ar", + ) # def test_print_ar_tatweel_itself(self): # FH, May 2022, sadly beginning a string with tatweel does not work @@ -192,45 +207,48 @@ def test_ask_ar_tatweel_all_places(self): def test_print_ar_tatweel_printing(self): code = "قول لــــ" expected = "print('لــــ')" - output = 'لــــ' + output = "لــــ" self.single_level_tester( code=code, expected=expected, output=output, translate=False, # translation will remove the tatweels, we will deal with that later - lang='ar') + lang="ar", + ) def test_print_ar_tatweel_begin(self): code = "ـــقول أ" expected = "print('أ')" - output = 'أ' + output = "أ" self.single_level_tester( code=code, expected=expected, output=output, translate=False, # translation will remove the tatweels, we will deal with that later - lang='ar') + lang="ar", + ) def test_print_ar_tatweel_multiple_end(self): code = "ـــقــوـلــــ أ" expected = "print('أ')" - output = 'أ' + output = "أ" self.single_level_tester( code=code, expected=expected, output=output, translate=False, # translation will remove the tatweels, we will deal with that later - lang='ar') + lang="ar", + ) def test_print_ar_2(self): code = "قول مرحبا أيها العالم!" expected = "print('مرحبا أيها العالم!')" - output = 'مرحبا أيها العالم!' + output = "مرحبا أيها العالم!" - self.single_level_tester(code=code, expected=expected, output=output, lang='ar') + self.single_level_tester(code=code, expected=expected, output=output, lang="ar") # # ask tests @@ -275,7 +293,7 @@ def test_ask_nl_code_transpiled_in_nl(self): code = "vraag Heb je er zin in?" expected = "answer = input('Heb je er zin in?')" - self.single_level_tester(code=code, expected=expected, lang='nl') + self.single_level_tester(code=code, expected=expected, lang="nl") def test_ask_en_code_transpiled_in_nl(self): code = "ask Heb je er zin in?" @@ -284,29 +302,33 @@ def test_ask_en_code_transpiled_in_nl(self): self.single_level_tester( code=code, expected=expected, - lang='nl', - translate=False # we are trying a Dutch keyword in en, can't be translated + lang="nl", + translate=False, # we are trying a Dutch keyword in en, can't be translated ) def test_mixes_languages_nl_en(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ vraag Heb je er zin in? echo ask are you sure? - print mooizo!""") + print mooizo!""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ answer = input('Heb je er zin in?') print(answer) answer = input('are you sure?') - print('mooizo!')""") + print('mooizo!')""" + ) self.single_level_tester( code=code, expected=expected, - expected_commands=['ask', 'echo', 'ask', 'print'], - lang='nl', - translate=False # mixed codes will not translate back to their original form, sadly + expected_commands=["ask", "echo", "ask", "print"], + lang="nl", + translate=False, # mixed codes will not translate back to their original form, sadly ) # @@ -319,13 +341,17 @@ def test_echo_without_argument(self): self.single_level_tester(code=code, expected=expected) def test_echo_with_quotes(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ ask waar? - echo oma's aan de""") + echo oma's aan de""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ answer = input('waar?') - print('oma\\'s aan de '+answer)""") + print('oma\\'s aan de '+answer)""" + ) self.single_level_tester(code=code, expected=expected) @@ -340,7 +366,7 @@ def test_forward(self): code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=11 + max_level=11, ) def test_forward_arabic_numeral(self): @@ -351,7 +377,7 @@ def test_forward_arabic_numeral(self): code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=11 + max_level=11, ) def test_forward_hindi_numeral(self): @@ -362,20 +388,22 @@ def test_forward_hindi_numeral(self): code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=11 + max_level=11, ) def test_forward_without_argument(self): - code = 'forward' - expected = textwrap.dedent("""\ + code = "forward" + expected = textwrap.dedent( + """\ t.forward(50) - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=11 + max_level=11, ) def test_forward_with_text_gives_type_error(self): @@ -384,23 +412,25 @@ def test_forward_with_text_gives_type_error(self): self.multi_level_tester( code=code, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1 + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, ) def test_multiple_forward_without_arguments(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ forward - forward""") - expected = textwrap.dedent("""\ + forward""" + ) + expected = textwrap.dedent( + """\ t.forward(50) time.sleep(0.1) t.forward(50) - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_turtle() + code=code, expected=expected, extra_check_function=self.is_turtle() ) # @@ -413,28 +443,35 @@ def test_color_no_args(self): code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=10) + max_level=10, + ) def test_one_color_red(self): code = "color red" expected = "t.pencolor('red')" - self.single_level_tester(code=code, expected=expected, - extra_check_function=self.is_turtle()) + self.single_level_tester( + code=code, expected=expected, extra_check_function=self.is_turtle() + ) def test_one_color_purple(self): code = "color purple" expected = "t.pencolor('purple')" - self.single_level_tester(code=code, expected=expected, - extra_check_function=self.is_turtle()) + self.single_level_tester( + code=code, expected=expected, extra_check_function=self.is_turtle() + ) def test_one_color_purple_nl(self): code = "kleur paars" expected = "t.pencolor('purple')" - self.single_level_tester(code=code, expected=expected, - extra_check_function=self.is_turtle(), lang='nl') + self.single_level_tester( + code=code, + expected=expected, + extra_check_function=self.is_turtle(), + lang="nl", + ) # # turn tests @@ -444,9 +481,7 @@ def test_turn_no_args(self): expected = "t.right(90)" self.multi_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_turtle() + code=code, expected=expected, extra_check_function=self.is_turtle() ) def test_turn_right(self): @@ -454,9 +489,7 @@ def test_turn_right(self): expected = "t.right(90)" self.single_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_turtle() + code=code, expected=expected, extra_check_function=self.is_turtle() ) def test_turn_left(self): @@ -464,9 +497,7 @@ def test_turn_left(self): expected = "t.left(90)" self.single_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_turtle() + code=code, expected=expected, extra_check_function=self.is_turtle() ) def test_turn_left_nl(self): @@ -477,7 +508,7 @@ def test_turn_left_nl(self): code=code, expected=expected, extra_check_function=self.is_turtle(), - lang='nl' + lang="nl", ) def test_turn_with_text_gives_error(self): @@ -489,13 +520,13 @@ def test_turn_with_text_gives_error(self): # we can add multiple tests to the skipped_mappings list to test multiple error mappings skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 13), hedy.exceptions.InvalidArgumentException) + SkippedMapping( + SourceRange(1, 1, 1, 13), hedy.exceptions.InvalidArgumentException + ) ] self.single_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings + code=code, expected=expected, skipped_mappings=skipped_mappings ) # @@ -510,53 +541,63 @@ def test_comment(self): def test_print_comment(self): code = "print Hallo welkom bij Hedy! # This is a print" expected = "print('Hallo welkom bij Hedy! ')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" self.single_level_tester( code=code, expected=expected, output=output, - expected_commands=[Command.print] + expected_commands=[Command.print], ) - self.assertEqual(['Hallo welkom bij Hedy! '], hedy.all_print_arguments(code, self.level)) + self.assertEqual( + ["Hallo welkom bij Hedy! "], hedy.all_print_arguments(code, self.level) + ) # # combined commands tests # def test_print_ask_echo(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print Hallo ask Wat is je lievelingskleur - echo je lievelingskleur is""") + echo je lievelingskleur is""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ print('Hallo') answer = input('Wat is je lievelingskleur') - print('je lievelingskleur is '+answer)""") + print('je lievelingskleur is '+answer)""" + ) self.single_level_tester( code=code, expected=expected, - expected_commands=[Command.print, Command.ask, Command.echo]) + expected_commands=[Command.print, Command.ask, Command.echo], + ) def test_forward_turn_combined(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ forward 50 turn - forward 100""") + forward 100""" + ) expected = HedyTester.dedent( HedyTester.forward_transpiled(50, self.level), - 't.right(90)', - HedyTester.forward_transpiled(100, self.level)) + "t.right(90)", + HedyTester.forward_transpiled(100, self.level), + ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), expected_commands=[Command.forward, Command.turn, Command.forward], - max_level=11 + max_level=11, ) # @@ -565,16 +606,20 @@ def test_forward_turn_combined(self): def test_lines_may_end_in_spaces(self): code = "print Hallo welkom bij Hedy! " expected = "print('Hallo welkom bij Hedy! ')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" - self.single_level_tester(code=code, expected=expected, output=output, translate=False) + self.single_level_tester( + code=code, expected=expected, output=output, translate=False + ) def test_comments_may_be_empty(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ # # This is a comment # - print Привіт, Хейді!""") + print Привіт, Хейді!""" + ) expected = "print('Привіт, Хейді!')" output = "Привіт, Хейді!" @@ -588,56 +633,58 @@ def test_print_with_space_gives_invalid(self): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 30), hedy.exceptions.InvalidSpaceException) + SkippedMapping( + SourceRange(1, 1, 1, 30), hedy.exceptions.InvalidSpaceException + ) ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=1) + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=1 + ) def test_ask_with_space_gives_invalid(self): code = " ask Hallo welkom bij Hedy?" expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 28), hedy.exceptions.InvalidSpaceException) + SkippedMapping( + SourceRange(1, 1, 1, 28), hedy.exceptions.InvalidSpaceException + ) ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=1) + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=1 + ) def test_lines_with_spaces_gives_invalid(self): code = " print Hallo welkom bij Hedy!\n print Hallo welkom bij Hedy!" expected = "pass\npass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 30), hedy.exceptions.InvalidSpaceException), - SkippedMapping(SourceRange(1, 1, 1, 30), hedy.exceptions.InvalidSpaceException), + SkippedMapping( + SourceRange(1, 1, 1, 30), hedy.exceptions.InvalidSpaceException + ), + SkippedMapping( + SourceRange(1, 1, 1, 30), hedy.exceptions.InvalidSpaceException + ), ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=3) + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=3 + ) def test_word_plus_period_gives_invalid(self): code = "word." expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 6), hedy.exceptions.MissingCommandException), + SkippedMapping( + SourceRange(1, 1, 1, 6), hedy.exceptions.MissingCommandException + ), ] self.single_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings + code=code, expected=expected, skipped_mappings=skipped_mappings ) def test_non_keyword_gives_invalid(self): @@ -645,13 +692,13 @@ def test_non_keyword_gives_invalid(self): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 6), hedy.exceptions.MissingCommandException), + SkippedMapping( + SourceRange(1, 1, 1, 6), hedy.exceptions.MissingCommandException + ), ] self.single_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings + code=code, expected=expected, skipped_mappings=skipped_mappings ) def test_lonely_echo_gives_LonelyEcho(self): @@ -659,43 +706,51 @@ def test_lonely_echo_gives_LonelyEcho(self): self.single_level_tester(code, exception=hedy.exceptions.LonelyEchoException) def test_echo_before_ask_gives_lonely_echo(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ echo what can't we do? - ask time travel """) + ask time travel """ + ) self.single_level_tester(code, exception=hedy.exceptions.LonelyEchoException) def test_pint_after_empty_line_gives_error_line_3(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print hallo - prnt hallo""") + prnt hallo""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ print('hallo') - pass""") + pass""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(3, 1, 3, 11), hedy.exceptions.InvalidCommandException), + SkippedMapping( + SourceRange(3, 1, 3, 11), hedy.exceptions.InvalidCommandException + ), ] self.single_level_tester( - code, - expected=expected, - skipped_mappings=skipped_mappings + code, expected=expected, skipped_mappings=skipped_mappings ) def test_print_without_argument_gives_incomplete(self): self.multi_level_tester( code="print", exception=hedy.exceptions.IncompleteCommandException, - extra_check_function=lambda c: c.exception.arguments['incomplete_command'] == 'print' + extra_check_function=lambda c: c.exception.arguments["incomplete_command"] + == "print", ) def test_print_without_argument_gives_incomplete_2(self): self.multi_level_tester( code="print lalalala\nprint", exception=hedy.exceptions.IncompleteCommandException, - extra_check_function=lambda c: c.exception.arguments['incomplete_command'] == 'print', + extra_check_function=lambda c: c.exception.arguments["incomplete_command"] + == "print", max_level=17, ) @@ -704,44 +759,51 @@ def test_non_keyword_with_argument_gives_invalid(self): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 18), hedy.exceptions.InvalidCommandException), + SkippedMapping( + SourceRange(1, 1, 1, 18), hedy.exceptions.InvalidCommandException + ), ] self.multi_level_tester( code=code, expected=expected, skipped_mappings=skipped_mappings, - extra_check_function=lambda c: c.arguments['invalid_command'] == 'aks', + extra_check_function=lambda c: c.arguments["invalid_command"] == "aks", max_level=5, ) def test_source_map(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print Hallo welkom bij Hedy! forward 50 ask Wat is je lievelingskleur - echo je lievelingskleur is""") + echo je lievelingskleur is""" + ) - expected_code = HedyTester.dedent(f"""\ + expected_code = HedyTester.dedent( + f"""\ print('Hallo welkom bij Hedy!') {HedyTester.indent( HedyTester.forward_transpiled(50, self.level), 8, True) } answer = input('Wat is je lievelingskleur') - print('je lievelingskleur is '+answer)""") + print('je lievelingskleur is '+answer)""" + ) expected_source_map = { - '1/1-1/29': '1/1-1/32', - '2/1-2/11': '2/1-8/16', - '3/1-3/30': '9/1-9/44', - '4/1-4/27': '10/1-10/39', - '1/1-4/28': '1/1-10/39' + "1/1-1/29": "1/1-1/32", + "2/1-2/11": "2/1-8/16", + "3/1-3/30": "9/1-9/44", + "4/1-4/27": "10/1-10/39", + "1/1-4/28": "1/1-10/39", } self.single_level_tester(code, expected=expected_code) self.source_map_tester(code=code, expected_source_map=expected_source_map) + # hypothesis initialization starts here @@ -757,41 +819,61 @@ def test_source_map(self): ("ask

", 1), ("echo

", 2), ("ask

", 3), - ("echo

", 4) + ("echo

", 4), ] def valid_permutation(lines): orders = [order for _, order in lines] significant_orders = [x for x in orders if x > 0] # -1 may be placed everywhere - list = [significant_orders[i] <= significant_orders[i+1] for i in range(len(significant_orders)-1)] + list = [ + significant_orders[i] <= significant_orders[i + 1] + for i in range(len(significant_orders) - 1) + ] return all(list) class TestsHypothesisLevel1(HedyTester): level = 1 - @given(code_tuples=hypothesis.strategies.permutations(templates), d=hypothesis.strategies.data()) + @given( + code_tuples=hypothesis.strategies.permutations(templates), + d=hypothesis.strategies.data(), + ) @settings(deadline=None, max_examples=100) # FH may 2023: we now always use a permutation, but a random sample which could potentially be smaller would be a nice addition! def test_template_combination(self, code_tuples, d): - excluded_chars = ["_", "#", '\n', '\r'] + excluded_chars = ["_", "#", "\n", "\r"] random_print_argument = hypothesis.strategies.text( - alphabet=hypothesis.strategies.characters(blacklist_characters=excluded_chars), + alphabet=hypothesis.strategies.characters( + blacklist_characters=excluded_chars + ), min_size=1, - max_size=10) + max_size=10, + ) if valid_permutation(code_tuples): - lines = [line.replace("

", d.draw(random_print_argument)) for line, _ in code_tuples] - code = '\n'.join(lines) - - self.single_level_tester( - code=code, - translate=False - ) - - expected_commands = [Command.ask, Command.ask, Command.echo, Command.echo, Command.forward, Command.forward, - Command.print, Command.print, Command.print, Command.turn, Command.turn] - - all_commands = sorted(hedy.all_commands(code, self.level, 'en')) + lines = [ + line.replace("

", d.draw(random_print_argument)) + for line, _ in code_tuples + ] + code = "\n".join(lines) + + self.single_level_tester(code=code, translate=False) + + expected_commands = [ + Command.ask, + Command.ask, + Command.echo, + Command.echo, + Command.forward, + Command.forward, + Command.print, + Command.print, + Command.print, + Command.turn, + Command.turn, + ] + + all_commands = sorted(hedy.all_commands(code, self.level, "en")) self.assertEqual(expected_commands, all_commands) diff --git a/tests/test_level/test_level_02.py b/tests/test_level/test_level_02.py index a7537f1db7d..b9017c6d17e 100644 --- a/tests/test_level/test_level_02.py +++ b/tests/test_level/test_level_02.py @@ -10,7 +10,7 @@ class TestsLevel2(HedyTester): level = 2 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -21,7 +21,7 @@ class TestsLevel2(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # print tests @@ -29,65 +29,84 @@ class TestsLevel2(HedyTester): def test_print(self): code = "print Hallo welkom bij Hedy!" expected = "print(f'Hallo welkom bij Hedy!')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" self.multi_level_tester( - code=code, - expected=expected, - output=output, - max_level=3 + code=code, expected=expected, output=output, max_level=3 ) def test_print_no_space(self): code = "printHallo welkom bij Hedy!" expected = "print(f'Hallo welkom bij Hedy!')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" - self.multi_level_tester(code=code, expected=expected, output=output, max_level=3) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=3 + ) def test_print_comma(self): code = "print welkom bij steen, schaar, papier" expected = "print(f'welkom bij steen, schaar, papier')" output = "welkom bij steen, schaar, papier" - self.multi_level_tester(code=code, expected=expected, output=output, max_level=3) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=3 + ) def test_print_multiple_lines(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print Hallo welkom bij Hedy! - print Mooi hoor""") + print Mooi hoor""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ print(f'Hallo welkom bij Hedy!') - print(f'Mooi hoor')""") + print(f'Mooi hoor')""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ Hallo welkom bij Hedy! - Mooi hoor""") + Mooi hoor""" + ) - self.multi_level_tester(code=code, expected=expected, output=output, max_level=3) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=3 + ) def test_print_var_with_comma(self): # test for issue 2549 - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ name is test - print name, heya!""") + print name, heya!""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ name = 'test' - print(f'{name}, heya!')""") + print(f'{name}, heya!')""" + ) - output = textwrap.dedent("""\ - test, heya!""") + output = textwrap.dedent( + """\ + test, heya!""" + ) - self.multi_level_tester(code=code, expected=expected, output=output, max_level=3) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=3 + ) def test_print_single_quoted_text(self): code = "print 'Welcome to OceanView'" expected = "print(f'\\'Welcome to OceanView\\'')" output = "'Welcome to OceanView'" - self.multi_level_tester(code=code, expected=expected, output=output, max_level=3) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=3 + ) def test_print_line_with_spaces_works(self): code = "print hallo\n \nprint hallo" @@ -98,25 +117,27 @@ def test_print_line_with_spaces_works(self): code=code, expected=expected, expected_commands=expected_commands, - max_level=3) + max_level=3, + ) def test_print_exclamation_mark_and_quote(self): # test for issue 279 code = "print hello world!'" expected = "print(f'hello world!\\'')" - output = "hello world!\'" + output = "hello world!'" - self.multi_level_tester(code=code, - expected=expected, - output=output, - max_level=3) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=3 + ) def test_print_double_quoted_text(self): code = 'print "Welcome to OceanView"' expected = "print(f'\"Welcome to OceanView\"')" output = '"Welcome to OceanView"' - self.multi_level_tester(code=code, expected=expected, output=output, max_level=3) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=3 + ) def test_print_text_with_inner_single_quote(self): code = "print Welcome to Hedy's game!" @@ -141,14 +162,18 @@ def test_print_backslash(self): expected = "print(f'Yes\\\\No')" output = "Yes\\No" - self.multi_level_tester(code=code, expected=expected, output=output, max_level=3) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=3 + ) def test_print_backslash_at_end(self): code = "print Welcome to \\" expected = "print(f'Welcome to \\\\')" output = "Welcome to \\" - self.multi_level_tester(code=code, expected=expected, output=output, max_level=3) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=3 + ) def test_print_with_spaces(self): code = "print hallo!" @@ -168,24 +193,20 @@ def test_print_asterisk(self): def test_print_comment(self): code = "print Hallo welkom bij Hedy! # This is a comment" expected = "print(f'Hallo welkom bij Hedy!')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" self.multi_level_tester( max_level=3, code=code, expected=expected, output=output, - expected_commands=[Command.print] + expected_commands=[Command.print], ) def test_assign_comment(self): code = "test is Welkom bij Hedy # This is a comment" expected = "test = 'Welkom bij Hedy '" - self.multi_level_tester( - max_level=3, - code=code, - expected=expected - ) + self.multi_level_tester(max_level=3, code=code, expected=expected) # # ask tests @@ -221,13 +242,17 @@ def test_ask_text_with_inner_double_quote(self): self.multi_level_tester(code=code, expected=expected, max_level=3) def test_ask_with_comma(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is ask hond, kat, kangoeroe - print dieren""") + print dieren""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ dieren = input('hond, kat, kangoeroe') - print(f'{dieren}')""") + print(f'{dieren}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=3) @@ -238,13 +263,17 @@ def test_ask_es(self): self.multi_level_tester(code=code, expected=expected, max_level=3) def test_ask_bengali_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ রং is ask আপনার প্রিয় রং কি? - print রং is আপনার প্রিয""") + print রং is আপনার প্রিয""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ রং = input('আপনার প্রিয় রং কি?') - print(f'{রং} is আপনার প্রিয')""") + print(f'{রং} is আপনার প্রিয')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=3) @@ -252,12 +281,14 @@ def test_ask_bengali_var(self): # forward tests # def test_forward_with_integer_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 50 - forward a""") + forward a""" + ) expected = HedyTester.dedent( - "a = '50'", - HedyTester.forward_transpiled('a', self.level)) + "a = '50'", HedyTester.forward_transpiled("a", self.level) + ) self.multi_level_tester( code=code, @@ -267,15 +298,17 @@ def test_forward_with_integer_variable(self): ) def test_forward_with_string_variable_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is test - forward a""") + forward a""" + ) self.multi_level_tester( code=code, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - max_level=11 + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + max_level=11, ) # @@ -289,7 +322,7 @@ def test_turn_number(self): code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=11 + max_level=11, ) def test_turn_negative_number(self): @@ -300,30 +333,35 @@ def test_turn_negative_number(self): code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=11 + max_level=11, ) def test_turn_with_number_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ direction is 70 - turn direction""") + turn direction""" + ) expected = HedyTester.dedent( - "direction = '70'", - HedyTester.turn_transpiled('direction', self.level)) + "direction = '70'", HedyTester.turn_transpiled("direction", self.level) + ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=11 + max_level=11, ) def test_turn_with_non_latin_number_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ الزاوية هو ٩٠ استدر الزاوية - تقدم ١٠٠""") - expected = textwrap.dedent("""\ + تقدم ١٠٠""" + ) + expected = textwrap.dedent( + """\ الزاوية = '٩٠' __trtl = الزاوية try: @@ -337,11 +375,12 @@ def test_turn_with_non_latin_number_var(self): except ValueError: raise Exception(f'While running your program the command forward received the value {__trtl} which is not allowed. Try changing the value to a number.') t.forward(min(600, __trtl) if __trtl > 0 else max(-600, __trtl)) - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( code=code, - lang='ar', + lang="ar", expected=expected, extra_check_function=self.is_turtle(), max_level=11, @@ -353,27 +392,31 @@ def test_one_turn_with_text_gives_type_error(self): self.multi_level_tester( code=code, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1 + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, ) - @parameterized.expand(['left', 'right']) + @parameterized.expand(["left", "right"]) def test_one_turn_with_left_or_right_gives_type_error(self, arg): code = f"turn {arg}" self.multi_level_tester( code=code, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1, - max_level=11 + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, + max_level=11, ) def test_access_before_assign_not_allowed(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print the name program - name is Hedy""") + name is Hedy""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ pass - name = 'Hedy'""") + name = 'Hedy'""" + ) skipped_mappings = [ SkippedMapping(SourceRange(1, 1, 1, 23), hedy.exceptions.AccessBeforeAssign) @@ -387,30 +430,34 @@ def test_access_before_assign_not_allowed(self): ) def test_turn_with_string_var_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ direction is ten - turn direction""") + turn direction""" + ) self.multi_level_tester( code=code, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, max_level=11, ) def test_turn_with_non_ascii_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ ángulo is 90 - turn ángulo""") + turn ángulo""" + ) expected = HedyTester.dedent( - "ángulo = '90'", - HedyTester.turn_transpiled('ángulo', self.level)) + "ángulo = '90'", HedyTester.turn_transpiled("ángulo", self.level) + ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - expected_commands=['is', 'turn'], + expected_commands=["is", "turn"], max_level=11, ) @@ -420,7 +467,9 @@ def test_turn_right_number_gives_type_error(self): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 15), hedy.exceptions.InvalidArgumentException), + SkippedMapping( + SourceRange(1, 1, 1, 15), hedy.exceptions.InvalidArgumentException + ), ] self.multi_level_tester( @@ -433,41 +482,42 @@ def test_turn_right_number_gives_type_error(self): # color tests def test_color_red(self): code = "color red" - expected = HedyTester.turtle_color_command_transpiled('red') + expected = HedyTester.turtle_color_command_transpiled("red") self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=10 + max_level=10, ) def test_color_with_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ foo is white - color foo""") + color foo""" + ) expected = HedyTester.dedent( - "foo = 'white'", - HedyTester.turtle_color_command_transpiled('{foo}') + "foo = 'white'", HedyTester.turtle_color_command_transpiled("{foo}") ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=10 + max_level=10, ) def test_color_translated(self): code = "kleur blauw" - expected = HedyTester.turtle_color_command_transpiled('blue') + expected = HedyTester.turtle_color_command_transpiled("blue") self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - lang='nl', - max_level=10 + lang="nl", + max_level=10, ) def test_color_with_number_gives_type_error(self): @@ -475,7 +525,7 @@ def test_color_with_number_gives_type_error(self): self.multi_level_tester( code=code, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1, + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, max_level=10, ) @@ -514,45 +564,54 @@ def test_sleep_with_number_ar(self): self.multi_level_tester(code=code, expected=expected) def test_sleep_with_number_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 2 - sleep n""") + sleep n""" + ) expected = HedyTester.dedent( - "n = '2'", - HedyTester.sleep_command_transpiled("n")) + "n = '2'", HedyTester.sleep_command_transpiled("n") + ) self.multi_level_tester(max_level=11, code=code, expected=expected) def test_sleep_with_number_variable_hi(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is २ - sleep n""") + sleep n""" + ) expected = HedyTester.dedent( - "n = '२'", - HedyTester.sleep_command_transpiled("n")) + "n = '२'", HedyTester.sleep_command_transpiled("n") + ) self.multi_level_tester(max_level=11, code=code, expected=expected) def test_sleep_with_input_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is ask how long - sleep n""") + sleep n""" + ) expected = HedyTester.dedent( - "n = input('how long')", - HedyTester.sleep_command_transpiled("n")) + "n = input('how long')", HedyTester.sleep_command_transpiled("n") + ) self.multi_level_tester(max_level=3, code=code, expected=expected) def test_sleep_with_string_variable_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is test - sleep n""") + sleep n""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) # # assign tests @@ -563,14 +622,13 @@ def test_assign_with_space_gives_invalid(self): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 14), hedy.exceptions.InvalidSpaceException), + SkippedMapping( + SourceRange(1, 1, 1, 14), hedy.exceptions.InvalidSpaceException + ), ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=5 + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=5 ) def test_assign(self): @@ -580,15 +638,19 @@ def test_assign(self): self.multi_level_tester(code=code, expected=expected, max_level=11) def test_assign_catalan_var_name(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print És hora una nit de Netflix pel·lícula is Sonic the Hedgehog 2 - print Veurem una pel·lícula""") + print Veurem una pel·lícula""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ print(f'És hora una nit de Netflix') pel·lícula = 'Sonic the Hedgehog 2' - print(f'Veurem una {pel·lícula}')""") + print(f'Veurem una {pel·lícula}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=3) @@ -623,13 +685,17 @@ def test_assign_text_with_inner_double_quote(self): self.multi_level_tester(code=code, expected=expected, max_level=11) def test_assign_text_to_hungarian_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ állatok is kutya - print állatok""") + print állatok""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ állatok = 'kutya' - print(f'{állatok}')""") + print(f'{állatok}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -651,8 +717,10 @@ def test_assign_python_keyword(self): # def test_spaces_in_arguments(self): code = "print hallo wereld" - expected = textwrap.dedent("""\ - print(f'hallo wereld')""") + expected = textwrap.dedent( + """\ + print(f'hallo wereld')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=3) @@ -660,86 +728,113 @@ def test_spaces_in_arguments(self): # combined tests # def test_ask_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ kleur is ask wat is je lievelingskleur? - print kleur!""") - expected = textwrap.dedent("""\ + print kleur!""" + ) + expected = textwrap.dedent( + """\ kleur = input('wat is je lievelingskleur?') - print(f'{kleur}!')""") + print(f'{kleur}!')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=3) def test_assign_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Felienne - print naam""") - expected = textwrap.dedent("""\ + print naam""" + ) + expected = textwrap.dedent( + """\ naam = 'Felienne' - print(f'{naam}')""") + print(f'{naam}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=3) def test_forward_ask(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ afstand is ask hoe ver dan? - forward afstand""") + forward afstand""" + ) expected = HedyTester.dedent( "afstand = input('hoe ver dan?')", - HedyTester.forward_transpiled('afstand', self.level)) + HedyTester.forward_transpiled("afstand", self.level), + ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=3 + max_level=3, ) def test_ask_turn(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print Turtle race direction is ask Where to turn? - turn direction""") + turn direction""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ print(f'Turtle race') direction = input('Where to turn?')""", - HedyTester.turn_transpiled('direction', self.level)) + HedyTester.turn_transpiled("direction", self.level), + ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=3 + max_level=3, ) def test_assign_print_punctuation(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy - print Hallo naam!""") + print Hallo naam!""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' - print(f'Hallo {naam}!')""") + print(f'Hallo {naam}!')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=3) def test_assign_print_sentence(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy - print naam is jouw voornaam""") - expected = textwrap.dedent("""\ + print naam is jouw voornaam""" + ) + expected = textwrap.dedent( + """\ naam = 'Hedy' - print(f'{naam} is jouw voornaam')""") + print(f'{naam} is jouw voornaam')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=3) def test_assign_print_something_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Felienne - print Hallo""") - expected = textwrap.dedent("""\ + print Hallo""" + ) + expected = textwrap.dedent( + """\ naam = 'Felienne' - print(f'Hallo')""") + print(f'Hallo')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=3) @@ -747,25 +842,33 @@ def test_assign_print_something_else(self): # negative tests # def test_echo_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ ask what is jouw lievelingskleur? - echo Jouw lievelingskleur is dus...""") + echo Jouw lievelingskleur is dus...""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ pass - pass""") + pass""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 34), hedy.exceptions.WrongLevelException), - SkippedMapping(SourceRange(2, 1, 2, 36), hedy.exceptions.WrongLevelException), + SkippedMapping( + SourceRange(1, 1, 1, 34), hedy.exceptions.WrongLevelException + ), + SkippedMapping( + SourceRange(2, 1, 2, 36), hedy.exceptions.WrongLevelException + ), ] self.multi_level_tester( code=code, expected=expected, skipped_mappings=skipped_mappings, - extra_check_function=lambda c: c.error_code == 'Wrong Level', - max_level=3 + extra_check_function=lambda c: c.error_code == "Wrong Level", + max_level=3, ) def test_ask_without_var_gives_error(self): @@ -773,7 +876,9 @@ def test_ask_without_var_gives_error(self): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 23), hedy.exceptions.WrongLevelException), + SkippedMapping( + SourceRange(1, 1, 1, 23), hedy.exceptions.WrongLevelException + ), ] self.multi_level_tester( @@ -788,5 +893,5 @@ def test_ask_without_argument_gives_error(self): self.multi_level_tester( max_level=17, code=code, - exception=hedy.exceptions.IncompleteCommandException + exception=hedy.exceptions.IncompleteCommandException, ) diff --git a/tests/test_level/test_level_03.py b/tests/test_level/test_level_03.py index c2c8c7ae07a..c504dbd3c18 100644 --- a/tests/test_level/test_level_03.py +++ b/tests/test_level/test_level_03.py @@ -7,7 +7,7 @@ class TestsLevel3(HedyTester): level = 3 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -18,7 +18,7 @@ class TestsLevel3(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # print tests @@ -26,127 +26,153 @@ class TestsLevel3(HedyTester): # issue #745 def test_print_list_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ plaatsen is een stad, een dorp, een strand - print plaatsen""") + print plaatsen""" + ) self.multi_level_tester( code=code, max_level=11, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_print_list_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is Hond, Kat, Kangoeroe - print dieren at random""") + print dieren at random""" + ) - expected = HedyTester.dedent("dieren = ['Hond', 'Kat', 'Kangoeroe']", - HedyTester.list_access_transpiled("random.choice(dieren)"), - "print(f'{random.choice(dieren)}')") + expected = HedyTester.dedent( + "dieren = ['Hond', 'Kat', 'Kangoeroe']", + HedyTester.list_access_transpiled("random.choice(dieren)"), + "print(f'{random.choice(dieren)}')", + ) # check if result is in the expected list - check_in_list = (lambda x: HedyTester.run_code(x) in ['Hond', 'Kat', 'Kangoeroe']) + def check_in_list(x): + return HedyTester.run_code(x) in ["Hond", "Kat", "Kangoeroe"] self.multi_level_tester( max_level=11, code=code, expected=expected, - extra_check_function=check_in_list + extra_check_function=check_in_list, ) def test_print_list_random_punctuation(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ gerechten is spaghetti, spruitjes, hamburgers - print Jij eet vanavond gerechten at random!""") - - expected = HedyTester.dedent("gerechten = ['spaghetti', 'spruitjes', 'hamburgers']", - HedyTester.list_access_transpiled("random.choice(gerechten)"), - "print(f'Jij eet vanavond {random.choice(gerechten)} !')") + print Jij eet vanavond gerechten at random!""" + ) - self.multi_level_tester( - max_level=3, - code=code, - expected=expected + expected = HedyTester.dedent( + "gerechten = ['spaghetti', 'spruitjes', 'hamburgers']", + HedyTester.list_access_transpiled("random.choice(gerechten)"), + "print(f'Jij eet vanavond {random.choice(gerechten)} !')", ) + self.multi_level_tester(max_level=3, code=code, expected=expected) + def test_print_list_random_punctuation_2(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ prijzen is 1 euro, 10 euro, 100 euro - print Dat wordt dan prijzen at random, alstublieft.""") - - expected = HedyTester.dedent("prijzen = ['1 euro', '10 euro', '100 euro']", - HedyTester.list_access_transpiled("random.choice(prijzen)"), - "print(f'Dat wordt dan {random.choice(prijzen)} , alstublieft.')") + print Dat wordt dan prijzen at random, alstublieft.""" + ) - self.multi_level_tester( - max_level=3, - code=code, - expected=expected + expected = HedyTester.dedent( + "prijzen = ['1 euro', '10 euro', '100 euro']", + HedyTester.list_access_transpiled("random.choice(prijzen)"), + "print(f'Dat wordt dan {random.choice(prijzen)} , alstublieft.')", ) + self.multi_level_tester(max_level=3, code=code, expected=expected) + def test_print_list_access_index(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is Hond, Kat, Kangoeroe - print dieren at 1""") + print dieren at 1""" + ) - expected = HedyTester.dedent("dieren = ['Hond', 'Kat', 'Kangoeroe']", - HedyTester.list_access_transpiled('dieren[int(1)-1]'), - "print(f'{dieren[int(1)-1]}')") + expected = HedyTester.dedent( + "dieren = ['Hond', 'Kat', 'Kangoeroe']", + HedyTester.list_access_transpiled("dieren[int(1)-1]"), + "print(f'{dieren[int(1)-1]}')", + ) - check_in_list = (lambda x: HedyTester.run_code(x) == 'Hond') + def check_in_list(x): + return HedyTester.run_code(x) == "Hond" self.multi_level_tester( max_level=11, code=code, expected=expected, - extra_check_function=check_in_list + extra_check_function=check_in_list, ) def test_print_list_random_fr(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ animaux est chien, chat, kangourou - affiche animaux au hasard""") + affiche animaux au hasard""" + ) - expected = HedyTester.dedent("animaux = ['chien', 'chat', 'kangourou']", - HedyTester.list_access_transpiled('random.choice(animaux)'), - "print(f'{random.choice(animaux)}')") + expected = HedyTester.dedent( + "animaux = ['chien', 'chat', 'kangourou']", + HedyTester.list_access_transpiled("random.choice(animaux)"), + "print(f'{random.choice(animaux)}')", + ) # check if result is in the expected list - check_in_list = (lambda x: HedyTester.run_code(x) in ['chien', 'chat', 'kangourou']) + def check_in_list(x): + return HedyTester.run_code(x) in [ + "chien", + "chat", + "kangourou", + ] self.multi_level_tester( max_level=11, code=code, expected=expected, extra_check_function=check_in_list, - lang='fr' + lang="fr", ) # # ask tests # def test_ask_list_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ plaatsen is een stad, een dorp, een strand - var is ask plaatsen""") + var is ask plaatsen""" + ) self.multi_level_tester( code=code, max_level=11, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) # # sleep tests # def test_sleep_with_list_access(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1, 2, 3 - sleep n at 1""") - expected = textwrap.dedent("""\ + sleep n at 1""" + ) + expected = textwrap.dedent( + """\ n = ['1', '2', '3'] try: try: @@ -155,16 +181,20 @@ def test_sleep_with_list_access(self): raise Exception('catch_index_exception') time.sleep(int(n[int(1)-1])) except ValueError: - raise Exception(f'While running your program the command sleep received the value {n[int(1)-1]} which is not allowed. Try changing the value to a number.')""") + raise Exception(f'While running your program the command sleep received the value {n[int(1)-1]} which is not allowed. Try changing the value to a number.')""" + ) self.multi_level_tester(max_level=11, code=code, expected=expected) def test_sleep_with_list_random(self): self.maxDiff = None - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1, 2, 3 - sleep n at random""") - expected = textwrap.dedent("""\ + sleep n at random""" + ) + expected = textwrap.dedent( + """\ n = ['1', '2', '3'] try: try: @@ -173,34 +203,42 @@ def test_sleep_with_list_random(self): raise Exception('catch_index_exception') time.sleep(int(random.choice(n))) except ValueError: - raise Exception(f'While running your program the command sleep received the value {random.choice(n)} which is not allowed. Try changing the value to a number.')""") + raise Exception(f'While running your program the command sleep received the value {random.choice(n)} which is not allowed. Try changing the value to a number.')""" + ) self.multi_level_tester(max_level=11, code=code, expected=expected) def test_sleep_with_list_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1, 2, 3 - sleep n""") + sleep n""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) # # assign tests # def test_assign_var_to_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dier1 is hond dier2 is dier1 - print dier2""") + print dier2""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ dier1 = 'hond' dier2 = dier1 - print(f'{dier2}')""") + print(f'{dier2}')""" + ) self.multi_level_tester(max_level=11, code=code, expected=expected) @@ -211,13 +249,17 @@ def test_assign_list(self): self.multi_level_tester(max_level=11, code=code, expected=expected) def test_assign_list_to_hungarian_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ állatok is kutya, macska, kenguru - print állatok at random""") + print állatok at random""" + ) - expected = HedyTester.dedent("állatok = ['kutya', 'macska', 'kenguru']", - HedyTester.list_access_transpiled("random.choice(állatok)"), - "print(f'{random.choice(állatok)}')") + expected = HedyTester.dedent( + "állatok = ['kutya', 'macska', 'kenguru']", + HedyTester.list_access_transpiled("random.choice(állatok)"), + "print(f'{random.choice(állatok)}')", + ) self.multi_level_tester(max_level=11, code=code, expected=expected) @@ -230,21 +272,25 @@ def test_assign_list_with_spaces(self): self.multi_level_tester(max_level=11, code=code, expected=expected) def test_assign_random_value(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is hond, kat, kangoeroe dier is dieren at random - print dier""") + print dier""" + ) - expected = HedyTester.dedent("dieren = ['hond', 'kat', 'kangoeroe']", - HedyTester.list_access_transpiled("random.choice(dieren)"), - "dier = random.choice(dieren)", - "print(f'{dier}')") + expected = HedyTester.dedent( + "dieren = ['hond', 'kat', 'kangoeroe']", + HedyTester.list_access_transpiled("random.choice(dieren)"), + "dier = random.choice(dieren)", + "print(f'{dier}')", + ) self.multi_level_tester( max_level=11, code=code, expected=expected, - extra_check_function=self.result_in(['Hond', 'Kat', 'Kangoeroe']) + extra_check_function=self.result_in(["Hond", "Kat", "Kangoeroe"]), ) def test_assign_list_with_dutch_comma_arabic_lang(self): @@ -255,85 +301,95 @@ def test_assign_list_with_dutch_comma_arabic_lang(self): max_level=11, code=code, expected=expected, - lang='ar', + lang="ar", # translation must be off because the Latin commas will be converted to arabic commas and this is correct - translate=False + translate=False, ) def test_assign_list_with_arabic_comma_and_is(self): code = "animals هو cat، dog، platypus" expected = "animals = ['cat', 'dog', 'platypus']" - self.multi_level_tester( - max_level=11, - code=code, - expected=expected, - lang='ar' - ) + self.multi_level_tester(max_level=11, code=code, expected=expected, lang="ar") def test_assign_list_with_arabic_comma(self): code = "صديقي هو احمد، خالد، حسن" expected = "صديقي = ['احمد', 'خالد', 'حسن']" - self.multi_level_tester( - max_level=11, - code=code, - expected=expected, - lang='ar' - ) + self.multi_level_tester(max_level=11, code=code, expected=expected, lang="ar") def test_assign_list_exclamation_mark(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoorden is ja, NEE!, misschien - print antwoorden at random""") + print antwoorden at random""" + ) - expected = HedyTester.dedent("antwoorden = ['ja', 'NEE!', 'misschien']", - HedyTester.list_access_transpiled("random.choice(antwoorden)"), - "print(f'{random.choice(antwoorden)}')") + expected = HedyTester.dedent( + "antwoorden = ['ja', 'NEE!', 'misschien']", + HedyTester.list_access_transpiled("random.choice(antwoorden)"), + "print(f'{random.choice(antwoorden)}')", + ) self.multi_level_tester(max_level=11, code=code, expected=expected) def test_assign_list_values_with_inner_single_quotes(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ taart is 'appeltaart, choladetaart, kwarktaart' - print 'we bakken een' taart at random""") + print 'we bakken een' taart at random""" + ) - expected = HedyTester.dedent("taart = ['\\'appeltaart', 'choladetaart', 'kwarktaart\\'']", - HedyTester.list_access_transpiled("random.choice(taart)"), - "print(f'\\'we bakken een\\' {random.choice(taart)}')") + expected = HedyTester.dedent( + "taart = ['\\'appeltaart', 'choladetaart', 'kwarktaart\\'']", + HedyTester.list_access_transpiled("random.choice(taart)"), + "print(f'\\'we bakken een\\' {random.choice(taart)}')", + ) self.single_level_tester(code=code, expected=expected) def test_assign_list_values_with_inner_double_quotes(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ taart is "appeltaart, choladetaart, kwarktaart" - print 'we bakken een' taart at random""") + print 'we bakken een' taart at random""" + ) - expected = HedyTester.dedent("taart = ['\"appeltaart', 'choladetaart', 'kwarktaart\"']", - HedyTester.list_access_transpiled("random.choice(taart)"), - "print(f'\\'we bakken een\\' {random.choice(taart)}')") + expected = HedyTester.dedent( + "taart = ['\"appeltaart', 'choladetaart', 'kwarktaart\"']", + HedyTester.list_access_transpiled("random.choice(taart)"), + "print(f'\\'we bakken een\\' {random.choice(taart)}')", + ) self.single_level_tester(code=code, expected=expected) def test_assign_list_with_single_quoted_values(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ taart is 'appeltaart', 'choladetaart', 'kwarktaart' - print 'we bakken een' taart at random""") + print 'we bakken een' taart at random""" + ) - expected = HedyTester.dedent("taart = ['\\'appeltaart\\'', '\\'choladetaart\\'', '\\'kwarktaart\\'']", - HedyTester.list_access_transpiled('random.choice(taart)'), - "print(f'\\'we bakken een\\' {random.choice(taart)}')") + expected = HedyTester.dedent( + "taart = ['\\'appeltaart\\'', '\\'choladetaart\\'', '\\'kwarktaart\\'']", + HedyTester.list_access_transpiled("random.choice(taart)"), + "print(f'\\'we bakken een\\' {random.choice(taart)}')", + ) self.single_level_tester(code=code, expected=expected) def test_assign_list_with_double_quoted_values(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ taart is "appeltaart", "choladetaart", "kwarktaart" - print "we bakken een" taart at random""") + print "we bakken een" taart at random""" + ) - expected = HedyTester.dedent("taart = ['\"appeltaart\"', '\"choladetaart\"', '\"kwarktaart\"']", - HedyTester.list_access_transpiled('random.choice(taart)'), - "print(f'\"we bakken een\" {random.choice(taart)}')") + expected = HedyTester.dedent( + "taart = ['\"appeltaart\"', '\"choladetaart\"', '\"kwarktaart\"']", + HedyTester.list_access_transpiled("random.choice(taart)"), + "print(f'\"we bakken een\" {random.choice(taart)}')", + ) self.single_level_tester(code=code, expected=expected) @@ -347,25 +403,31 @@ def test_assign_list_values_with_inner_quotes(self): # forward tests # def test_forward_with_list_variable_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 1, 2, 3 - forward a""") + forward a""" + ) self.multi_level_tester( max_level=15, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_forward_with_list_access_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ directions is 10, 100, 360 - forward directions at random""") + forward directions at random""" + ) - expected = HedyTester.dedent("directions = ['10', '100', '360']", - HedyTester.list_access_transpiled('random.choice(directions)'), - HedyTester.forward_transpiled('random.choice(directions)', self.level)) + expected = HedyTester.dedent( + "directions = ['10', '100', '360']", + HedyTester.list_access_transpiled("random.choice(directions)"), + HedyTester.forward_transpiled("random.choice(directions)", self.level), + ) print(expected) self.multi_level_tester( max_level=11, @@ -378,25 +440,31 @@ def test_forward_with_list_access_random(self): # turn tests # def test_turn_with_list_variable_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 45, 90, 180 - turn a""") + turn a""" + ) self.multi_level_tester( max_level=15, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_turn_with_list_access_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ directions is 10, 100, 360 - turn directions at random""") + turn directions at random""" + ) - expected = HedyTester.dedent("directions = ['10', '100', '360']", - HedyTester.list_access_transpiled('random.choice(directions)'), - HedyTester.turn_transpiled('random.choice(directions)', self.level)) + expected = HedyTester.dedent( + "directions = ['10', '100', '360']", + HedyTester.list_access_transpiled("random.choice(directions)"), + HedyTester.turn_transpiled("random.choice(directions)", self.level), + ) self.multi_level_tester( max_level=11, @@ -409,25 +477,31 @@ def test_turn_with_list_access_random(self): # color tests # def test_color_with_list_variable_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ c is red, green, blue - color c""") + color c""" + ) self.multi_level_tester( max_level=10, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_color_with_list_access_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is red, green, blue - color colors at random""") + color colors at random""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ colors = ['red', 'green', 'blue']""", - HedyTester.turtle_color_command_transpiled('{random.choice(colors)}')) + HedyTester.turtle_color_command_transpiled("{random.choice(colors)}"), + ) self.multi_level_tester( max_level=10, @@ -440,54 +514,64 @@ def test_color_with_list_access_random(self): # combined tests # def test_list_access_misspelled_at_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is Hond, Kat, Kangoeroe - print dieren ad random""") + print dieren ad random""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) # # add/remove tests # def test_add_text_to_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is koe, kiep add muis to dieren - print dieren at random""") + print dieren at random""" + ) - expected = HedyTester.dedent("dieren = ['koe', 'kiep']", - "dieren.append('muis')", - HedyTester.list_access_transpiled("random.choice(dieren)"), - "print(f'{random.choice(dieren)}')") + expected = HedyTester.dedent( + "dieren = ['koe', 'kiep']", + "dieren.append('muis')", + HedyTester.list_access_transpiled("random.choice(dieren)"), + "print(f'{random.choice(dieren)}')", + ) self.multi_level_tester( code=code, expected=expected, max_level=11, - extra_check_function=self.result_in(['koe', 'kiep', 'muis']), + extra_check_function=self.result_in(["koe", "kiep", "muis"]), ) # add/remove tests (IMAN) # def test_add_text_to_list_numerical(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ numbers is 1, 2 remove 1 from numbers remove 2 from numbers add 4 to numbers - print numbers at random""") + print numbers at random""" + ) - expected = HedyTester.dedent("numbers = ['1', '2']", - HedyTester.remove_transpiled('numbers', "'1'"), - HedyTester.remove_transpiled('numbers', "'2'"), - "numbers.append('4')", - HedyTester.list_access_transpiled("random.choice(numbers)"), - "print(f'{random.choice(numbers)}')") + expected = HedyTester.dedent( + "numbers = ['1', '2']", + HedyTester.remove_transpiled("numbers", "'1'"), + HedyTester.remove_transpiled("numbers", "'2'"), + "numbers.append('4')", + HedyTester.list_access_transpiled("random.choice(numbers)"), + "print(f'{random.choice(numbers)}')", + ) self.multi_level_tester( code=code, @@ -497,141 +581,173 @@ def test_add_text_to_list_numerical(self): ) def test_add_text_with_inner_single_quote_to_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is koe, kiep add mui's to dieren - print dieren at random""") + print dieren at random""" + ) - expected = HedyTester.dedent("dieren = ['koe', 'kiep']", - "dieren.append('mui\\\'s')", - HedyTester.list_access_transpiled("random.choice(dieren)"), - "print(f'{random.choice(dieren)}')") + expected = HedyTester.dedent( + "dieren = ['koe', 'kiep']", + "dieren.append('mui\\'s')", + HedyTester.list_access_transpiled("random.choice(dieren)"), + "print(f'{random.choice(dieren)}')", + ) self.multi_level_tester( code=code, expected=expected, max_level=11, - extra_check_function=self.result_in(['koe', 'kiep', 'mui\'s']), + extra_check_function=self.result_in(["koe", "kiep", "mui's"]), ) def test_add_text_with_inner_double_quote_to_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is koe, kiep add mui"s to dieren - print dieren at random""") + print dieren at random""" + ) - expected = HedyTester.dedent("dieren = ['koe', 'kiep']", - "dieren.append('mui\"s')", - HedyTester.list_access_transpiled("random.choice(dieren)"), - "print(f'{random.choice(dieren)}')") + expected = HedyTester.dedent( + "dieren = ['koe', 'kiep']", + "dieren.append('mui\"s')", + HedyTester.list_access_transpiled("random.choice(dieren)"), + "print(f'{random.choice(dieren)}')", + ) self.multi_level_tester( code=code, expected=expected, max_level=11, - extra_check_function=self.result_in(['koe', 'kiep', 'mui\"s']), + extra_check_function=self.result_in(["koe", "kiep", 'mui"s']), ) def test_add_integer_to_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is koe, kiep add 5 to dieren - print dieren at random""") + print dieren at random""" + ) - expected = HedyTester.dedent("dieren = ['koe', 'kiep']", - "dieren.append('5')", - HedyTester.list_access_transpiled("random.choice(dieren)"), - "print(f'{random.choice(dieren)}')") + expected = HedyTester.dedent( + "dieren = ['koe', 'kiep']", + "dieren.append('5')", + HedyTester.list_access_transpiled("random.choice(dieren)"), + "print(f'{random.choice(dieren)}')", + ) self.multi_level_tester( code=code, expected=expected, max_level=11, - extra_check_function=self.result_in(['koe', 'kiep', 5]), + extra_check_function=self.result_in(["koe", "kiep", 5]), ) def test_remove_text_from_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is koe, kiep remove kiep from dieren - print dieren at random""") + print dieren at random""" + ) - expected = HedyTester.dedent("dieren = ['koe', 'kiep']", - HedyTester.remove_transpiled('dieren', "'kiep'"), - HedyTester.list_access_transpiled('random.choice(dieren)'), - "print(f'{random.choice(dieren)}')") + expected = HedyTester.dedent( + "dieren = ['koe', 'kiep']", + HedyTester.remove_transpiled("dieren", "'kiep'"), + HedyTester.list_access_transpiled("random.choice(dieren)"), + "print(f'{random.choice(dieren)}')", + ) self.multi_level_tester( code=code, expected=expected, max_level=11, - extra_check_function=self.result_in(['koe']), + extra_check_function=self.result_in(["koe"]), ) def test_remove_text_with_single_quote_from_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is koe, kiep's remove kiep's from dieren - print dieren at random""") - expected = HedyTester.dedent("dieren = ['koe', 'kiep\\\'s']", - HedyTester.remove_transpiled('dieren', "'kiep\\\'s'"), - HedyTester.list_access_transpiled('random.choice(dieren)'), - "print(f'{random.choice(dieren)}')") + print dieren at random""" + ) + expected = HedyTester.dedent( + "dieren = ['koe', 'kiep\\'s']", + HedyTester.remove_transpiled("dieren", "'kiep\\'s'"), + HedyTester.list_access_transpiled("random.choice(dieren)"), + "print(f'{random.choice(dieren)}')", + ) self.multi_level_tester( code=code, expected=expected, max_level=11, - extra_check_function=self.result_in(['koe', 'kiep', 'mui\'s']), + extra_check_function=self.result_in(["koe", "kiep", "mui's"]), ) def test_remove_text_with_double_quote_from_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is koe, kiep"s remove kiep"s from dieren - print dieren at random""") + print dieren at random""" + ) - expected = HedyTester.dedent("dieren = ['koe', 'kiep\"s']", - HedyTester.remove_transpiled('dieren', "'kiep\"s'"), - HedyTester.list_access_transpiled("random.choice(dieren)"), - "print(f'{random.choice(dieren)}')") + expected = HedyTester.dedent( + "dieren = ['koe', 'kiep\"s']", + HedyTester.remove_transpiled("dieren", "'kiep\"s'"), + HedyTester.list_access_transpiled("random.choice(dieren)"), + "print(f'{random.choice(dieren)}')", + ) self.multi_level_tester( code=code, expected=expected, max_level=11, - extra_check_function=self.result_in(['koe', 'kiep', 'mui\'s']), + extra_check_function=self.result_in(["koe", "kiep", "mui's"]), ) def test_add_text_with_spaces_to_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ opties is zeker weten, misschien wel add absoluut niet to opties - print opties at random""") - - expected = HedyTester.dedent("opties = ['zeker weten', 'misschien wel']", - "opties.append('absoluut niet')", - HedyTester.list_access_transpiled("random.choice(opties)"), - "print(f'{random.choice(opties)}')") + print opties at random""" + ) - self.multi_level_tester( - max_level=11, - code=code, - expected=expected + expected = HedyTester.dedent( + "opties = ['zeker weten', 'misschien wel']", + "opties.append('absoluut niet')", + HedyTester.list_access_transpiled("random.choice(opties)"), + "print(f'{random.choice(opties)}')", ) + self.multi_level_tester(max_level=11, code=code, expected=expected) + def test_access_before_assign_with_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print colors at random - colors is green, red, blue""") + colors is green, red, blue""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ print(f'pass') - colors = ['green', 'red', 'blue']""") + colors = ['green', 'red', 'blue']""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(1, 7, 1, 13), hedy.exceptions.AccessBeforeAssign), - SkippedMapping(SourceRange(1, 7, 1, 23), hedy.exceptions.UndefinedVarException), + SkippedMapping( + SourceRange(1, 7, 1, 13), hedy.exceptions.AccessBeforeAssign + ), + SkippedMapping( + SourceRange(1, 7, 1, 23), hedy.exceptions.UndefinedVarException + ), ] self.single_level_tester( @@ -641,142 +757,172 @@ def test_access_before_assign_with_random(self): ) def test_add_ask_to_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is ask what is your favorite color? colors is green, red, blue add color to colors - print colors at random""") + print colors at random""" + ) - expected = HedyTester.dedent("color = input('what is your favorite color?')", - "colors = ['green', 'red', 'blue']", - "colors.append(color)", - HedyTester.list_access_transpiled("random.choice(colors)"), - "print(f'{random.choice(colors)}')") + expected = HedyTester.dedent( + "color = input('what is your favorite color?')", + "colors = ['green', 'red', 'blue']", + "colors.append(color)", + HedyTester.list_access_transpiled("random.choice(colors)"), + "print(f'{random.choice(colors)}')", + ) self.single_level_tester(code=code, expected=expected) def test_remove_ask_from_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is green, red, blue color is ask what color to remove? remove color from colors - print colors at random""") + print colors at random""" + ) - expected = HedyTester.dedent("colors = ['green', 'red', 'blue']", - "color = input('what color to remove?')", - HedyTester.remove_transpiled('colors', 'color'), - HedyTester.list_access_transpiled('random.choice(colors)'), - "print(f'{random.choice(colors)}')") + expected = HedyTester.dedent( + "colors = ['green', 'red', 'blue']", + "color = input('what color to remove?')", + HedyTester.remove_transpiled("colors", "color"), + HedyTester.list_access_transpiled("random.choice(colors)"), + "print(f'{random.choice(colors)}')", + ) self.single_level_tester(code=code, expected=expected) def test_add_to_list_with_string_var_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is yellow colors is green, red, blue - add colors to color""") + add colors to color""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_add_to_list_with_input_var_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is ask 'What are the colors?' favorite is red - add favorite to colors""") + add favorite to colors""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_remove_from_list_with_string_var_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is yellow colors is green, red, blue - remove colors from color""") + remove colors from color""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_remove_from_list_with_input_var_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is ask 'What are the colors?' favorite is red - remove favorite from colors""") + remove favorite from colors""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) # # negative tests # def test_random_from_string_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ items is aap noot mies - print items at random""") + print items at random""" + ) self.multi_level_tester( code=code, max_level=11, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_random_undefined_var_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is hond, kat, kangoeroe - print dier at random""") + print dier at random""" + ) self.multi_level_tester( code=code, max_level=11, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.UndefinedVarException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.UndefinedVarException, ) def test_list_access_with_type_input_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ animals is ask 'What are the animals?' - print animals at random""") + print animals at random""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_3778_at_index(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ l is 1, 2, 3 - print l at 3""") - expected = HedyTester.dedent("l = ['1', '2', '3']", - HedyTester.list_access_transpiled("l[int(3)-1]"), - "print(f'{l[int(3)-1]}')") + print l at 3""" + ) + expected = HedyTester.dedent( + "l = ['1', '2', '3']", + HedyTester.list_access_transpiled("l[int(3)-1]"), + "print(f'{l[int(3)-1]}')", + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_3778_at_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ l is 1, 2, 3 - print l at random""") - expected = HedyTester.dedent("l = ['1', '2', '3']", - HedyTester.list_access_transpiled("random.choice(l)"), - "print(f'{random.choice(l)}')") + print l at random""" + ) + expected = HedyTester.dedent( + "l = ['1', '2', '3']", + HedyTester.list_access_transpiled("random.choice(l)"), + "print(f'{random.choice(l)}')", + ) self.multi_level_tester(code=code, expected=expected, max_level=11) diff --git a/tests/test_level/test_level_04.py b/tests/test_level/test_level_04.py index e6865cb2859..120471677a8 100644 --- a/tests/test_level/test_level_04.py +++ b/tests/test_level/test_level_04.py @@ -10,7 +10,7 @@ class TestsLevel4(HedyTester): level = 4 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -21,7 +21,7 @@ class TestsLevel4(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # print tests @@ -30,46 +30,31 @@ def test_print_single_quoted_text(self): code = "print 'hallo wereld!'" expected = "print(f'hallo wereld!')" - self.multi_level_tester( - code=code, - max_level=11, - expected=expected) + self.multi_level_tester(code=code, max_level=11, expected=expected) def test_print_french_quoted_text(self): code = "print «bonjour tous le monde!»" expected = "print(f'bonjour tous le monde!')" - self.multi_level_tester( - code=code, - max_level=11, - expected=expected) + self.multi_level_tester(code=code, max_level=11, expected=expected) def test_print_chinese_quoted_text(self): code = "print “逃离鬼屋!”" expected = "print(f'逃离鬼屋!')" - self.multi_level_tester( - code=code, - max_level=11, - expected=expected) + self.multi_level_tester(code=code, max_level=11, expected=expected) def test_print_chinese_double_quoted_text(self): code = "print ‘逃离鬼屋!’" expected = "print(f'逃离鬼屋!')" - self.multi_level_tester( - code=code, - max_level=11, - expected=expected) + self.multi_level_tester(code=code, max_level=11, expected=expected) def test_print_double_quoted_text(self): code = 'print "hallo wereld!"' expected = "print(f'hallo wereld!')" - self.multi_level_tester( - code=code, - max_level=11, - expected=expected) + self.multi_level_tester(code=code, max_level=11, expected=expected) def test_print_line_with_spaces_works(self): code = "print 'hallo'\n \nprint 'hallo'" @@ -80,58 +65,45 @@ def test_print_line_with_spaces_works(self): code=code, expected=expected, expected_commands=expected_commands, - max_level=7) + max_level=7, + ) def test_print_single_quoted_text_with_inner_double_quote(self): code = """print 'quote is "'""" expected = """print(f'quote is "')""" - self.multi_level_tester( - code=code, - max_level=11, - expected=expected) + self.multi_level_tester(code=code, max_level=11, expected=expected) def test_print_double_quoted_text_with_inner_single_quote(self): code = '''print "It's me"''' expected = """print(f'It\\'s me')""" - self.multi_level_tester( - code=code, - max_level=11, - expected=expected) + self.multi_level_tester(code=code, max_level=11, expected=expected) def test_print_with_space_gives_invalid(self): code = " print 'Hallo welkom bij Hedy!'" expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 32), hedy.exceptions.InvalidSpaceException), + SkippedMapping( + SourceRange(1, 1, 1, 32), hedy.exceptions.InvalidSpaceException + ), ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=5 + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=5 ) def test_print_no_space(self): code = "print'hallo wereld!'" expected = "print(f'hallo wereld!')" - self.multi_level_tester( - code=code, - max_level=11, - expected=expected) + self.multi_level_tester(code=code, max_level=11, expected=expected) def test_print_comma(self): code = "print 'Hi, I am Hedy'" expected = "print(f'Hi, I am Hedy')" - self.multi_level_tester( - code=code, - max_level=11, - expected=expected - ) + self.multi_level_tester(code=code, max_level=11, expected=expected) def test_print_slash(self): code = "print 'Yes/No'" @@ -145,32 +117,21 @@ def test_print_backslash(self): output = "Yes\\No" self.multi_level_tester( - code=code, - expected=expected, - output=output, - max_level=11, - translate=True + code=code, expected=expected, output=output, max_level=11, translate=True ) def test_print_with_backslash_at_end(self): code = "print 'Welcome to \\'" expected = "print(f'Welcome to \\\\')" self.multi_level_tester( - code=code, - max_level=11, - expected=expected, - translate=True + code=code, max_level=11, expected=expected, translate=True ) def test_print_with_spaces(self): code = "print 'hallo!'" expected = "print(f'hallo!')" - self.multi_level_tester( - code=code, - max_level=11, - expected=expected - ) + self.multi_level_tester(code=code, max_level=11, expected=expected) def test_print_asterisk(self): code = "print '*Jouw* favoriet is dus kleur'" @@ -186,7 +147,9 @@ def test_print_without_quotes_gives_error_from_grammar(self): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 15), hedy.exceptions.UnquotedTextException), + SkippedMapping( + SourceRange(1, 1, 1, 15), hedy.exceptions.UnquotedTextException + ), ] self.multi_level_tester( @@ -217,7 +180,9 @@ def test_ask_without_quotes_gives_error_from_grammar(self): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 23), hedy.exceptions.UnquotedTextException), + SkippedMapping( + SourceRange(1, 1, 1, 23), hedy.exceptions.UnquotedTextException + ), ] self.multi_level_tester( @@ -228,13 +193,17 @@ def test_ask_without_quotes_gives_error_from_grammar(self): ) def test_assign_catalan_var_name(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ pel·lícula is Sonic the Hedgehog 2 - print 'Veurem una ' pel·lícula""") + print 'Veurem una ' pel·lícula""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ pel·lícula = 'Sonic the Hedgehog 2' - print(f'Veurem una {pel·lícula}')""") + print(f'Veurem una {pel·lícula}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -245,8 +214,8 @@ def test_place_holder_no_space(self): self.multi_level_tester( code=code, max_level=11, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1, - exception=hedy.exceptions.CodePlaceholdersPresentException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, + exception=hedy.exceptions.CodePlaceholdersPresentException, ) def test_ask_without_quotes_gives_error_from_transpiler(self): @@ -265,15 +234,17 @@ def test_print_similar_var_gives_error(self): # a quick analysis of the logs shows that in most cases quotes are forgotten # so we will only raise var if there is a variable that is a bit similar (see next test) - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ werld is ask 'tegen wie zeggen we hallo?' - print hallo wereld""") + print hallo wereld""" + ) self.multi_level_tester( code=code, max_level=17, exception=hedy.exceptions.UndefinedVarException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2 + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, ) @parameterized.expand(HedyTester.quotes) @@ -282,14 +253,13 @@ def test_print_without_opening_quote_gives_error(self, q): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 16), hedy.exceptions.UnquotedTextException), + SkippedMapping( + SourceRange(1, 1, 1, 16), hedy.exceptions.UnquotedTextException + ), ] self.multi_level_tester( - code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=5 + code, expected=expected, skipped_mappings=skipped_mappings, max_level=5 ) @parameterized.expand(HedyTester.quotes) @@ -298,57 +268,60 @@ def test_print_without_closing_quote_gives_error(self, q): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 16), hedy.exceptions.UnquotedTextException), + SkippedMapping( + SourceRange(1, 1, 1, 16), hedy.exceptions.UnquotedTextException + ), ] self.multi_level_tester( - code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=5 + code, expected=expected, skipped_mappings=skipped_mappings, max_level=5 ) def test_print_single_quoted_text_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 'Hedy' - print 'ik heet ' naam""") + print 'ik heet ' naam""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = '\\'Hedy\\'' - print(f'ik heet {naam}')""") - - self.multi_level_tester( - max_level=11, - code=code, - expected=expected + print(f'ik heet {naam}')""" ) + self.multi_level_tester(max_level=11, code=code, expected=expected) + def test_print_double_quoted_text_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is "Hedy" - print 'ik heet ' naam""") + print 'ik heet ' naam""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = '"Hedy"' - print(f'ik heet {naam}')""") - - self.multi_level_tester( - max_level=11, - code=code, - expected=expected + print(f'ik heet {naam}')""" ) + self.multi_level_tester(max_level=11, code=code, expected=expected) + # issue 1795 def test_print_quoted_var_reference(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 'Daan' woord1 is zomerkamp - print 'naam' ' is naar het' 'woord1'""") + print 'naam' ' is naar het' 'woord1'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = '\\'Daan\\'' woord1 = 'zomerkamp' - print(f'naam is naar hetwoord1')""") + print(f'naam is naar hetwoord1')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -358,23 +331,16 @@ def test_print_quoted_var_reference(self): def test_print_comment(self): code = "print 'Hallo welkom bij Hedy!' # This is a comment" expected = "print(f'Hallo welkom bij Hedy!')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" self.multi_level_tester( - max_level=11, - code=code, - expected=expected, - output=output + max_level=11, code=code, expected=expected, output=output ) def test_assign_comment(self): code = 'test is "Welkom bij Hedy" # This is a comment' - expected = 'test = \'"Welkom bij Hedy" \'' - self.multi_level_tester( - max_level=11, - code=code, - expected=expected - ) + expected = "test = '\"Welkom bij Hedy\" '" + self.multi_level_tester(max_level=11, code=code, expected=expected) # # ask tests @@ -399,7 +365,7 @@ def test_ask_single_quoted_text_with_inner_double_quote(self): def test_ask_double_quoted_text_with_inner_single_quote(self): code = f'''details is ask "say 'no'"''' - expected = '''details = input(f'say \\'no\\'')''' + expected = """details = input(f'say \\'no\\'')""" self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -408,13 +374,13 @@ def test_ask_without_quotes_gives_error(self): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 22), hedy.exceptions.UnquotedTextException), + SkippedMapping( + SourceRange(1, 1, 1, 22), hedy.exceptions.UnquotedTextException + ), ] self.single_level_tester( - code, - expected=expected, - skipped_mappings=skipped_mappings + code, expected=expected, skipped_mappings=skipped_mappings ) def test_ask_text_without_quotes_gives_error(self): @@ -432,13 +398,13 @@ def test_ask_without_opening_quote_gives_error(self, q): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 23), hedy.exceptions.UnquotedTextException), + SkippedMapping( + SourceRange(1, 1, 1, 23), hedy.exceptions.UnquotedTextException + ), ] self.single_level_tester( - code, - expected=expected, - skipped_mappings=skipped_mappings + code, expected=expected, skipped_mappings=skipped_mappings ) @parameterized.expand(HedyTester.quotes) @@ -447,23 +413,27 @@ def test_ask_without_closing_quote_gives_error(self, q): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 23), hedy.exceptions.UnquotedTextException), + SkippedMapping( + SourceRange(1, 1, 1, 23), hedy.exceptions.UnquotedTextException + ), ] self.single_level_tester( - code, - expected=expected, - skipped_mappings=skipped_mappings + code, expected=expected, skipped_mappings=skipped_mappings ) def test_ask_with_comma(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is ask 'hond, kat, kangoeroe' - print dieren""") + print dieren""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ dieren = input(f'hond, kat, kangoeroe') - print(f'{dieren}')""") + print(f'{dieren}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -476,76 +446,101 @@ def test_ask_es(self, q): @parameterized.expand(HedyTester.quotes) def test_ask_bengali_var(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ রং is ask {q}আপনার প্রিয় রং কি?{q} - print রং {q} is আপনার প্রিয{q}""") + print রং {q} is আপনার প্রিয{q}""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ রং = input(f'আপনার প্রিয় রং কি?') - print(f'{রং} is আপনার প্রিয')""") + print(f'{রং} is আপনার প্রিয')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_ask_list_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is orange, blue, green - favorite is ask 'Is your fav color ' colors at random""") + favorite is ask 'Is your fav color ' colors at random""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ colors = ['orange', 'blue', 'green'] - favorite = input(f'Is your fav color {random.choice(colors)}')""") + favorite = input(f'Is your fav color {random.choice(colors)}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_print_list_access_index_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ index is 1 dieren is Hond, Kat, Kangoeroe - print dieren at index""") + print dieren at index""" + ) - expected = HedyTester.dedent("index = '1'\ndieren = ['Hond', 'Kat', 'Kangoeroe']", - HedyTester.list_access_transpiled('dieren[int(index)-1]'), - "print(f'{dieren[int(index)-1]}')") + expected = HedyTester.dedent( + "index = '1'\ndieren = ['Hond', 'Kat', 'Kangoeroe']", + HedyTester.list_access_transpiled("dieren[int(index)-1]"), + "print(f'{dieren[int(index)-1]}')", + ) - check_in_list = (lambda x: HedyTester.run_code(x) == 'Hond') + def check_in_list(x): + return HedyTester.run_code(x) == "Hond" self.multi_level_tester( max_level=11, code=code, expected=expected, - extra_check_function=check_in_list + extra_check_function=check_in_list, ) def test_ask_list_access_index(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is orange, blue, green - favorite is ask 'Is your fav color ' colors at 1""") + favorite is ask 'Is your fav color ' colors at 1""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ colors = ['orange', 'blue', 'green'] - favorite = input(f'Is your fav color {colors[int(1)-1]}')""") + favorite = input(f'Is your fav color {colors[int(1)-1]}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_ask_string_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is orange - favorite is ask 'Is your fav color ' color""") + favorite is ask 'Is your fav color ' color""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ color = 'orange' - favorite = input(f'Is your fav color {color}')""") + favorite = input(f'Is your fav color {color}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_ask_integer_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ number is 10 - favorite is ask 'Is your fav number' number""") + favorite is ask 'Is your fav number' number""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ number = '10' - favorite = input(f'Is your fav number{number}')""") + favorite = input(f'Is your fav number{number}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -553,12 +548,14 @@ def test_ask_integer_var(self): # sleep tests # def test_sleep_with_input_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is ask "how long" - sleep n""") + sleep n""" + ) expected = HedyTester.dedent( - "n = input(f'how long')", - HedyTester.sleep_command_transpiled("n")) + "n = input(f'how long')", HedyTester.sleep_command_transpiled("n") + ) self.multi_level_tester(max_level=11, code=code, expected=expected) @@ -566,24 +563,32 @@ def test_sleep_with_input_variable(self): # assign tests # def test_assign_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy - print 'ik heet' naam""") + print 'ik heet' naam""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' - print(f'ik heet{naam}')""") + print(f'ik heet{naam}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_assign_underscore(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ voor_naam is Hedy - print 'ik heet ' voor_naam""") + print 'ik heet ' voor_naam""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ voor_naam = 'Hedy' - print(f'ik heet {voor_naam}')""") + print(f'ik heet {voor_naam}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -594,46 +599,62 @@ def test_assign_period(self): self.multi_level_tester(code=code, expected=expected, max_level=11) def test_assign_list_values_with_inner_single_quotes(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ taart is 'appeltaart, choladetaart, kwarktaart' - print 'we bakken een ' taart at random""") + print 'we bakken een ' taart at random""" + ) - expected = HedyTester.dedent("taart = ['\\'appeltaart', 'choladetaart', 'kwarktaart\\'']", - HedyTester.list_access_transpiled('random.choice(taart)'), - "print(f'we bakken een {random.choice(taart)}')") + expected = HedyTester.dedent( + "taart = ['\\'appeltaart', 'choladetaart', 'kwarktaart\\'']", + HedyTester.list_access_transpiled("random.choice(taart)"), + "print(f'we bakken een {random.choice(taart)}')", + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_assign_list_values_with_inner_double_quotes(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ taart is "appeltaart, choladetaart, kwarktaart" - print 'we bakken een ' taart at random""") + print 'we bakken een ' taart at random""" + ) - expected = HedyTester.dedent("taart = ['\"appeltaart', 'choladetaart', 'kwarktaart\"']", - HedyTester.list_access_transpiled('random.choice(taart)'), - "print(f'we bakken een {random.choice(taart)}')") + expected = HedyTester.dedent( + "taart = ['\"appeltaart', 'choladetaart', 'kwarktaart\"']", + HedyTester.list_access_transpiled("random.choice(taart)"), + "print(f'we bakken een {random.choice(taart)}')", + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_assign_list_with_single_quoted_values(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ taart is 'appeltaart', 'choladetaart', 'kwarktaart' - print 'we bakken een' taart at random""") + print 'we bakken een' taart at random""" + ) - expected = HedyTester.dedent("taart = ['\\'appeltaart\\'', '\\'choladetaart\\'', '\\'kwarktaart\\'']", - HedyTester.list_access_transpiled('random.choice(taart)'), - "print(f'we bakken een{random.choice(taart)}')") + expected = HedyTester.dedent( + "taart = ['\\'appeltaart\\'', '\\'choladetaart\\'', '\\'kwarktaart\\'']", + HedyTester.list_access_transpiled("random.choice(taart)"), + "print(f'we bakken een{random.choice(taart)}')", + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_assign_list_with_double_quoted_values(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ taart is "appeltaart, choladetaart, kwarktaart" - print 'we bakken een' taart at random""") + print 'we bakken een' taart at random""" + ) - expected = HedyTester.dedent("taart = ['\"appeltaart', 'choladetaart', 'kwarktaart\"']", - HedyTester.list_access_transpiled('random.choice(taart)'), - "print(f'we bakken een{random.choice(taart)}')") + expected = HedyTester.dedent( + "taart = ['\"appeltaart', 'choladetaart', 'kwarktaart\"']", + HedyTester.list_access_transpiled("random.choice(taart)"), + "print(f'we bakken een{random.choice(taart)}')", + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -651,31 +672,39 @@ def test_assign_double_quoted_text(self): # add/remove tests # def test_add_ask_to_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is ask 'what is your favorite color?' colors is green, red, blue add color to colors - print colors at random""") - expected = HedyTester.dedent("color = input(f'what is your favorite color?')", - "colors = ['green', 'red', 'blue']", - "colors.append(color)", - HedyTester.list_access_transpiled("random.choice(colors)"), - "print(f'{random.choice(colors)}')") + print colors at random""" + ) + expected = HedyTester.dedent( + "color = input(f'what is your favorite color?')", + "colors = ['green', 'red', 'blue']", + "colors.append(color)", + HedyTester.list_access_transpiled("random.choice(colors)"), + "print(f'{random.choice(colors)}')", + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_remove_ask_from_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is green, red, blue color is ask 'what color to remove?' remove color from colors - print colors at random""") + print colors at random""" + ) - expected = HedyTester.dedent("colors = ['green', 'red', 'blue']", - "color = input(f'what color to remove?')", - HedyTester.remove_transpiled('colors', 'color'), - HedyTester.list_access_transpiled('random.choice(colors)'), - "print(f'{random.choice(colors)}')") + expected = HedyTester.dedent( + "colors = ['green', 'red', 'blue']", + "color = input(f'what color to remove?')", + HedyTester.remove_transpiled("colors", "color"), + HedyTester.list_access_transpiled("random.choice(colors)"), + "print(f'{random.choice(colors)}')", + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -683,72 +712,81 @@ def test_remove_ask_from_list(self): # combined tests # def test_assign_print_chinese(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ 你世界 is 你好世界 - print 你世界""") + print 你世界""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ 你世界 = '你好世界' - print(f'{你世界}')""") + print(f'{你世界}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_ask_forward(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ afstand is ask 'hoe ver dan?' - forward afstand""") + forward afstand""" + ) expected = HedyTester.dedent( "afstand = input(f'hoe ver dan?')", - HedyTester.forward_transpiled('afstand', self.level)) + HedyTester.forward_transpiled("afstand", self.level), + ) self.multi_level_tester( max_level=11, code=code, expected=expected, - extra_check_function=self.is_turtle() + extra_check_function=self.is_turtle(), ) # # negative tests # def test_var_undefined_error_message(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy - print 'ik heet ' name""") + print 'ik heet ' name""" + ) self.multi_level_tester( - code=code, - max_level=11, - exception=hedy.exceptions.UndefinedVarException + code=code, max_level=11, exception=hedy.exceptions.UndefinedVarException ) # issue 375 def test_program_gives_hedy_parse_exception(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ is Foobar - print welcome""") + print welcome""" + ) self.multi_level_tester( code=code, max_level=11, exception=hedy.exceptions.ParseException, - extra_check_function=lambda c: c.exception.error_location[0] == 1 and c.exception.error_location[1] == 1 + extra_check_function=lambda c: c.exception.error_location[0] == 1 + and c.exception.error_location[1] == 1, ) def test_quoted_text_gives_error(self): - code = 'competitie die gaan we winnen' + code = "competitie die gaan we winnen" expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 31), hedy.exceptions.MissingCommandException), + SkippedMapping( + SourceRange(1, 1, 1, 31), hedy.exceptions.MissingCommandException + ), ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=5 + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=5 ) def test_repair_incorrect_print_argument(self): @@ -757,7 +795,7 @@ def test_repair_incorrect_print_argument(self): self.multi_level_tester( code=code, exception=hedy.exceptions.ParseException, - extra_check_function=lambda c: c.exception.fixed_code == "print 'Hello'" + extra_check_function=lambda c: c.exception.fixed_code == "print 'Hello'", ) def test_lonely_text(self): @@ -765,19 +803,19 @@ def test_lonely_text(self): expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 8), hedy.exceptions.LonelyTextException), + SkippedMapping( + SourceRange(1, 1, 1, 8), hedy.exceptions.LonelyTextException + ), ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=5 + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=5 ) def test_clear(self): code = "clear" - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ extensions.clear() try: # If turtle is being used, reset canvas @@ -786,28 +824,33 @@ def test_clear(self): t.left(90) t.showturtle() except NameError: - pass""") + pass""" + ) self.multi_level_tester(code=code, expected=expected) def test_source_map(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print 'You need to use quotation marks from now on!' answer is ask 'What do we need to use from now on?' - print 'We need to use ' answer""") + print 'We need to use ' answer""" + ) - expected_code = textwrap.dedent("""\ + expected_code = textwrap.dedent( + """\ print(f'You need to use quotation marks from now on!') answer = input(f'What do we need to use from now on?') - print(f'We need to use {answer}')""") + print(f'We need to use {answer}')""" + ) expected_source_map = { - '1/1-1/53': '1/1-1/55', - '2/1-2/7': '2/1-2/7', - '2/1-2/52': '2/1-2/55', - '3/25-3/31': '3/25-3/31', - '3/1-3/31': '3/1-3/34', - '1/1-3/32': '1/1-3/34' + "1/1-1/53": "1/1-1/55", + "2/1-2/7": "2/1-2/7", + "2/1-2/52": "2/1-2/55", + "3/25-3/31": "3/25-3/31", + "3/1-3/31": "3/1-3/34", + "1/1-3/32": "1/1-3/34", } self.single_level_tester(code, expected=expected_code) diff --git a/tests/test_level/test_level_05.py b/tests/test_level/test_level_05.py index 60bd1f93043..58d852b09d4 100644 --- a/tests/test_level/test_level_05.py +++ b/tests/test_level/test_level_05.py @@ -8,7 +8,7 @@ class TestsLevel5(HedyTester): level = 5 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -19,640 +19,795 @@ class TestsLevel5(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # if tests # @parameterized.expand(HedyTester.commands_level_4) - def test_if_equality_linebreak_print(self, hedy, python): + def test_if_equality_linebreak_print(self, hedy, python): # line breaks after if-condition are allowed - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam is Hedy if naam is Hedy - {hedy}""") + {hedy}""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ naam = 'Hedy' if naam == 'Hedy': - {python}""") + {python}""" + ) self.single_level_tester(code=code, expected=expected) def test_if_equality_trailing_space_linebreak_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is James if naam is trailing_space - print 'shaken'""") + print 'shaken'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if naam == 'trailing_space': - print(f'shaken')""") + print(f'shaken')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_equality_unquoted_rhs_with_space_linebreak_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is James if naam is James Bond - print 'shaken'""") + print 'shaken'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if naam == 'James Bond': - print(f'shaken')""") + print(f'shaken')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) - def test_if_equality_unquoted_rhs_with_space_and_trailing_space_linebreak_print(self): - code = textwrap.dedent("""\ + def test_if_equality_unquoted_rhs_with_space_and_trailing_space_linebreak_print( + self, + ): + code = textwrap.dedent( + """\ naam is James if naam is trailing space - print 'shaken'""") + print 'shaken'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if naam == 'trailing space': - print(f'shaken')""") + print(f'shaken')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_2_vars_equality_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ jouwkeuze is schaar computerkeuze is schaar - if computerkeuze is jouwkeuze print 'gelijkspel!'""") + if computerkeuze is jouwkeuze print 'gelijkspel!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ jouwkeuze = 'schaar' computerkeuze = 'schaar' if computerkeuze == jouwkeuze: - print(f'gelijkspel!')""") + print(f'gelijkspel!')""" + ) - self.single_level_tester(code=code, expected=expected, output='gelijkspel!') + self.single_level_tester(code=code, expected=expected, output="gelijkspel!") def test_if_not_in_list_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ items is red, green selected is red if selected not in items print 'not found!' - else print 'found'""") + else print 'found'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ items = ['red', 'green'] selected = 'red' if selected not in items: print(f'not found!') else: - print(f'found')""") + print(f'found')""" + ) self.multi_level_tester( - max_level=7, - code=code, - expected=expected, - output='found' + max_level=7, code=code, expected=expected, output="found" ) def test_if_not_in_list_in_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ items is red, green selected is purple if selected not in items print 'not found!' - else print 'found'""") + else print 'found'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ items = ['red', 'green'] selected = 'purple' if selected not in items: print(f'not found!') else: - print(f'found')""") + print(f'found')""" + ) self.multi_level_tester( - max_level=7, - code=code, - expected=expected, - output='not found!' + max_level=7, code=code, expected=expected, output="not found!" ) def test_if_in_list_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ items is red, green selected is red - if selected in items print 'found!'""") + if selected in items print 'found!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ items = ['red', 'green'] selected = 'red' if selected in items: - print(f'found!')""") + print(f'found!')""" + ) self.multi_level_tester( max_level=7, code=code, expected=expected, - output='found!', - expected_commands=['is', 'is', 'if', 'in', 'print'] + output="found!", + expected_commands=["is", "is", "if", "in", "print"], ) def test_if_in_undefined_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ selected is red - if selected in items print 'found!'""") + if selected in items print 'found!'""" + ) - self.multi_level_tester(code=code, exception=hedy.exceptions.UndefinedVarException, max_level=7) + self.multi_level_tester( + code=code, exception=hedy.exceptions.UndefinedVarException, max_level=7 + ) def test_if_equality_unquoted_rhs_with_space_print_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is James - if naam is James Bond print 'shaken'""") + if naam is James Bond print 'shaken'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' - pass""") + pass""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(2, 1, 2, 37), hedy.exceptions.UnquotedEqualityCheck), + SkippedMapping( + SourceRange(2, 1, 2, 37), hedy.exceptions.UnquotedEqualityCheck + ), ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=7 + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=7 ) - def test_if_equality_unquoted_rhs_with_space_and_following_command_print_gives_error(self): - code = textwrap.dedent("""\ + def test_if_equality_unquoted_rhs_with_space_and_following_command_print_gives_error( + self, + ): + code = textwrap.dedent( + """\ naam is James if naam is James Bond print 'shaken' - print naam""") + print naam""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' pass - print(f'{naam}')""") + print(f'{naam}')""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(2, 1, 2, 59), hedy.exceptions.UnquotedEqualityCheck), + SkippedMapping( + SourceRange(2, 1, 2, 59), hedy.exceptions.UnquotedEqualityCheck + ), ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=7 + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=7 ) def test_if_equality_unquoted_rhs_with_space_assign_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is James - if naam is James Bond naam is 'Pietjansma'""") + if naam is James Bond naam is 'Pietjansma'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' - pass""") + pass""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(2, 1, 2, 43), hedy.exceptions.UnquotedEqualityCheck), + SkippedMapping( + SourceRange(2, 1, 2, 43), hedy.exceptions.UnquotedEqualityCheck + ), ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=7 + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=7 ) @parameterized.expand(HedyTester.quotes) def test_if_equality_quoted_rhs_with_space(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam is James - if naam is {q}James Bond{q} print {q}shaken{q}""") + if naam is {q}James Bond{q} print {q}shaken{q}""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ naam = 'James' if naam == 'James Bond': - print(f'shaken')""") + print(f'shaken')""" + ) self.single_level_tester( - code=code, - expected_commands=['is', 'if', 'print'], - expected=expected) + code=code, expected_commands=["is", "if", "print"], expected=expected + ) @parameterized.expand(HedyTester.quotes) def test_if_equality_quoted_rhs_with_spaces(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam is James - if naam is {q}Bond James Bond{q} print 'shaken'""") + if naam is {q}Bond James Bond{q} print 'shaken'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ naam = 'James' if naam == 'Bond James Bond': - print(f'shaken')""") + print(f'shaken')""" + ) self.single_level_tester(code=code, expected=expected) def test_ask_if(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ name is ask 'what is your name?' - if name is Hedy print 'nice' else print 'boo!'""") + if name is Hedy print 'nice' else print 'boo!'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ name = input(f'what is your name?') if name == 'Hedy': print(f'nice') else: - print(f'boo!')""") + print(f'boo!')""" + ) self.single_level_tester( code=code, - expected_commands=['ask', 'if', 'else', 'print', 'print'], - expected=expected) + expected_commands=["ask", "if", "else", "print", "print"], + expected=expected, + ) def test_if_equality_single_quoted_rhs_with_inner_double_quote(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ answer is no - if answer is 'He said "no"' print 'no'""") + if answer is 'He said "no"' print 'no'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ answer = 'no' if answer == 'He said "no"': - print(f'no')""") + print(f'no')""" + ) self.single_level_tester( - code=code, - expected_commands=['is', 'if', 'print'], - expected=expected) + code=code, expected_commands=["is", "if", "print"], expected=expected + ) def test_if_equality_double_quoted_rhs_with_inner_single_quote(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ answer is no - if answer is "He said 'no'" print 'no'""") + if answer is "He said 'no'" print 'no'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ answer = 'no' if answer == 'He said \\'no\\'': - print(f'no')""") + print(f'no')""" + ) self.single_level_tester(code=code, expected=expected) def test_equality_promotes_int_to_string(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is test b is 15 - if a is b c is 1""") - expected = textwrap.dedent("""\ + if a is b c is 1""" + ) + expected = textwrap.dedent( + """\ a = 'test' b = '15' if a == b: - c = '1'""") + c = '1'""" + ) self.single_level_tester(code=code, expected=expected) def test_quoted_ask(self): - code = textwrap.dedent("""\ - szogek is ask 'Hello'""") + code = textwrap.dedent( + """\ + szogek is ask 'Hello'""" + ) expected = "szogek = input(f'Hello')" - self.multi_level_tester(code=code, - expected=expected, - max_level=11) + self.multi_level_tester(code=code, expected=expected, max_level=11) def test_equality_with_lists_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1, 2 m is 1, 2 - if n is m print 'success!'""") + if n is m print 'success!'""" + ) self.multi_level_tester( max_level=7, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_if_in_list_with_string_var_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ items is red - if red in items print 'found!'""") + if red in items print 'found!'""" + ) self.multi_level_tester( max_level=7, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_if_in_list_with_input_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ items is ask 'What are the items?' - if red in items print 'found!'""") + if red in items print 'found!'""" + ) self.multi_level_tester( max_level=7, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) # # if else tests # def test_if_equality_print_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy - if naam is Hedy print 'leuk' else print 'minder leuk'""") + if naam is Hedy print 'leuk' else print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if naam == 'Hedy': print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_ask_equality_print_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ kleur is ask 'Wat is je lievelingskleur?' - if kleur is groen print 'mooi!' else print 'niet zo mooi'""") + if kleur is groen print 'mooi!' else print 'niet zo mooi'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ kleur = input(f'Wat is je lievelingskleur?') if kleur == 'groen': print(f'mooi!') else: - print(f'niet zo mooi')""") + print(f'niet zo mooi')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_else_followed_by_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ kleur is geel if kleur is groen antwoord is ok else antwoord is stom - print antwoord""") + print antwoord""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ kleur = 'geel' if kleur == 'groen': antwoord = 'ok' else: antwoord = 'stom' - print(f'{antwoord}')""") + print(f'{antwoord}')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_equality_trailing_space_linebreak_print_else(self): # this code has a space at the end of line 2 - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is James if naam is trailing space - print 'shaken' else print 'biertje!'""") + print 'shaken' else print 'biertje!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if naam == 'trailing space': print(f'shaken') else: - print(f'biertje!')""") + print(f'biertje!')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_print_linebreak_else_print(self): # line break before else is allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' - else print 'minder leuk'""") + else print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if naam == 'Hedy': print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_equality_linebreak_print_else_print(self): # line break after if-condition is allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy - print 'leuk' else print 'minder leuk'""") + print 'leuk' else print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if naam == 'Hedy': print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_equality_unquoted_with_space_linebreak_print_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is James if naam is James Bond - print 'shaken' else print 'biertje!'""") + print 'shaken' else print 'biertje!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if naam == 'James Bond': print(f'shaken') else: - print(f'biertje!')""") + print(f'biertje!')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_two_ifs_assign_no_following(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if order is fries price is 5 - drink is water""") + drink is water""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ if 'order' == 'fries': price = '5' else: x__x__x__x = '5' - drink = 'water'""") + drink = 'water'""" + ) self.single_level_tester(code=code, expected=expected, translate=False) def test_two_ifs_assign_following(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if order is fries price is 5 drink is water - print drink""") + print drink""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ if 'order' == 'fries': price = '5' else: x__x__x__x = '5' drink = 'water' - print(f'{drink}')""") + print(f'{drink}')""" + ) self.single_level_tester(code=code, expected=expected, translate=False) def test_if_equality_print_else_linebreak_print(self): # line break after else is allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' else - print 'minder leuk'""") + print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if naam == 'Hedy': print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_with_negative_number(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord is -10 - if antwoord is -10 print 'Nice' else print 'Oh no'""") + if antwoord is -10 print 'Nice' else print 'Oh no'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ antwoord = '-10' if antwoord == '-10': print(f'Nice') else: - print(f'Oh no')""") + print(f'Oh no')""" + ) - self.single_level_tester(code=code, expected=expected, output='Nice') + self.single_level_tester(code=code, expected=expected, output="Nice") def test_if_equality_linebreak_print_linebreak_else_print(self): # line breaks after if-condition and before else are allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' - else print 'minder leuk'""") + else print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if naam == 'Hedy': print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_equality_linebreak_print_linebreak_else_linebreak_print(self): # line breaks after if-condition, before else and after else are allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' else - print 'minder leuk'""") + print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if naam == 'Hedy': print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_equality_linebreak_print_else_linebreak_print(self): # line breaks after if-condition and after else are allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' else - print 'minder leuk'""") + print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if naam == 'Hedy': print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_list_assignment_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ people is mom, dad, Emma, Sophie dishwasher is people at random if dishwasher is Sophie print 'too bad I have to do the dishes' else - print 'luckily no dishes because' dishwasher 'is already washing up'""") + print 'luckily no dishes because' dishwasher 'is already washing up'""" + ) - expected = HedyTester.dedent("people = ['mom', 'dad', 'Emma', 'Sophie']", - HedyTester.list_access_transpiled('random.choice(people)'), - "dishwasher = random.choice(people)", - "if dishwasher == 'Sophie':", - ("print(f'too bad I have to do the dishes')", ' '), - "else:", - ("print(f'luckily no dishes because{dishwasher}is already washing up')", ' ')) + expected = HedyTester.dedent( + "people = ['mom', 'dad', 'Emma', 'Sophie']", + HedyTester.list_access_transpiled("random.choice(people)"), + "dishwasher = random.choice(people)", + "if dishwasher == 'Sophie':", + ("print(f'too bad I have to do the dishes')", " "), + "else:", + ( + "print(f'luckily no dishes because{dishwasher}is already washing up')", + " ", + ), + ) self.single_level_tester(code=code, expected=expected) @parameterized.expand(HedyTester.quotes) def test_if_equality_quoted_rhs_with_space_else(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam is James - if naam is {q}James Bond{q} print {q}shaken{q} else print {q}biertje!{q}""") + if naam is {q}James Bond{q} print {q}shaken{q} else print {q}biertje!{q}""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ naam = 'James' if naam == 'James Bond': print(f'shaken') else: - print(f'biertje!')""") + print(f'biertje!')""" + ) self.single_level_tester(code=code, expected=expected) - def test_if_equality_text_with_spaces_and_single_quotes_linebreak_print_else_print(self): - code = textwrap.dedent("""\ + def test_if_equality_text_with_spaces_and_single_quotes_linebreak_print_else_print( + self, + ): + code = textwrap.dedent( + """\ naam is James if naam is James 'Bond' - print 'shaken' else print 'biertje!'""") + print 'shaken' else print 'biertje!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if naam == 'James \\'Bond\\'': print(f'shaken') else: - print(f'biertje!')""") + print(f'biertje!')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) - def test_if_equality_text_with_spaces_and_double_quotes_linebreak_print_else_print(self): - code = textwrap.dedent("""\ + def test_if_equality_text_with_spaces_and_double_quotes_linebreak_print_else_print( + self, + ): + code = textwrap.dedent( + """\ naam is James if naam is James "Bond" - print 'shaken' else print 'biertje!'""") + print 'shaken' else print 'biertje!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if naam == 'James "Bond"': print(f'shaken') else: - print(f'biertje!')""") + print(f'biertje!')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_print_else_print_bengali(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ নাম is ask 'আপনার নাম কি?' - if নাম is হেডি print 'ভালো!' else print 'মন্দ'""") + if নাম is হেডি print 'ভালো!' else print 'মন্দ'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ নাম = input(f'আপনার নাম কি?') if নাম == 'হেডি': print(f'ভালো!') else: - print(f'মন্দ')""") + print(f'মন্দ')""" + ) self.single_level_tester(code=code, expected=expected) @@ -661,13 +816,16 @@ def test_if_equality_print_else_print_bengali(self): # def test_consecutive_if_statements(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ names is Hedy, Lamar name is ask 'What is a name you like?' if name is Hedy print 'nice!' - if name in names print 'nice!'""") + if name in names print 'nice!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ names = ['Hedy', 'Lamar'] name = input(f'What is a name you like?') if name == 'Hedy': @@ -675,36 +833,43 @@ def test_consecutive_if_statements(self): else: x__x__x__x = '5' if name in names: - print(f'nice!')""") + print(f'nice!')""" + ) self.single_level_tester(code=code, expected=expected) def test_onno_3372(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antw is ask 'wat kies jij' if antw is schaar print 'gelijk spel!' - print 'test'""") + print 'test'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ antw = input(f'wat kies jij') if antw == 'schaar': print(f'gelijk spel!') else: x__x__x__x = '5' - print(f'test')""") + print(f'test')""" + ) - self.single_level_tester(code=code, - expected=expected) + self.single_level_tester(code=code, expected=expected) def test_restaurant_example(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print 'Welkom bij McHedy' eten is ask 'Wat wilt u eten?' if eten is friet saus is ask 'Welke saus wilt u bij de friet?' if eten is pizza topping is ask 'Welke topping wilt u op de pizza?' - print eten""") + print eten""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ print(f'Welkom bij McHedy') eten = input(f'Wat wilt u eten?') if eten == 'friet': @@ -715,36 +880,42 @@ def test_restaurant_example(self): topping = input(f'Welke topping wilt u op de pizza?') else: x__x__x__x = '5' - print(f'{eten}')""") + print(f'{eten}')""" + ) - self.single_level_tester(code=code, - expected=expected, translate=False) + self.single_level_tester(code=code, expected=expected, translate=False) def test_onno_3372_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antw is ask 'wat kies jij' if antw is schaar print 'gelijk spel!' else print '' - print 'test'""") + print 'test'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ antw = input(f'wat kies jij') if antw == 'schaar': print(f'gelijk spel!') else: print(f'') - print(f'test')""") + print(f'test')""" + ) - self.single_level_tester(code=code, - expected=expected) + self.single_level_tester(code=code, expected=expected) def test_consecutive_if_and_if_else_statements(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is ask 'hoe heet jij?' if naam is Hedy print 'leuk' if naam is Python print 'ook leuk' - else print 'minder leuk!'""") + else print 'minder leuk!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = input(f'hoe heet jij?') if naam == 'Hedy': print(f'leuk') @@ -753,19 +924,22 @@ def test_consecutive_if_and_if_else_statements(self): if naam == 'Python': print(f'ook leuk') else: - print(f'minder leuk!')""") + print(f'minder leuk!')""" + ) - self.single_level_tester(code=code, - expected=expected) + self.single_level_tester(code=code, expected=expected) def test_consecutive_if_else_statements(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ names is Hedy, Lamar name is ask 'What is a name you like?' if name is Hedy print 'nice!' else print 'meh' - if name in names print 'nice!' else print 'meh'""") + if name in names print 'nice!' else print 'meh'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ names = ['Hedy', 'Lamar'] name = input(f'What is a name you like?') if name == 'Hedy': @@ -775,57 +949,68 @@ def test_consecutive_if_else_statements(self): if name in names: print(f'nice!') else: - print(f'meh')""") + print(f'meh')""" + ) self.single_level_tester(code=code, expected=expected) def test_turn_if_forward(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ angle is 90, 180, 270 direction is angle at random turn direction - if direction is 180 forward 100""") + if direction is 180 forward 100""" + ) expected = HedyTester.dedent( """\ angle = ['90', '180', '270']""", - HedyTester.list_access_transpiled('random.choice(angle)'), + HedyTester.list_access_transpiled("random.choice(angle)"), "direction = random.choice(angle)", - HedyTester.turn_transpiled('direction', self.level), + HedyTester.turn_transpiled("direction", self.level), "if direction == '180':", - (HedyTester.forward_transpiled(100, self.level), ' ')) + (HedyTester.forward_transpiled(100, self.level), " "), + ) self.single_level_tester(code=code, expected=expected) def test_turn_if_forward_else_forward(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ angle is 90, 180, 270 direction is angle at random if direction is 180 forward direction - else turn direction""") + else turn direction""" + ) expected = HedyTester.dedent( """\ angle = ['90', '180', '270']""", - HedyTester.list_access_transpiled('random.choice(angle)'), + HedyTester.list_access_transpiled("random.choice(angle)"), "direction = random.choice(angle)", "if direction == '180':", - (HedyTester.forward_transpiled('direction', self.level), ' '), + (HedyTester.forward_transpiled("direction", self.level), " "), "else:", - (HedyTester.turn_transpiled('direction', self.level), ' ')) + (HedyTester.turn_transpiled("direction", self.level), " "), + ) self.single_level_tester(code=code, expected=expected) def test_list_access_index(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ friends is Hedy, Lola, Frida friend is friends at 2 - print friend""") + print friend""" + ) - expected = HedyTester.dedent("friends = ['Hedy', 'Lola', 'Frida']", - HedyTester.list_access_transpiled('friends[int(2)-1]'), - "friend = friends[int(2)-1]", - "print(f'{friend}')") + expected = HedyTester.dedent( + "friends = ['Hedy', 'Lola', 'Frida']", + HedyTester.list_access_transpiled("friends[int(2)-1]"), + "friend = friends[int(2)-1]", + "print(f'{friend}')", + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -834,151 +1019,172 @@ def test_list_access_index(self): # def test_if_indent_gives_parse_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ option is ask 'Rock Paper or Scissors?' if option is Scissors - print 'Its a tie!'""") + print 'Its a tie!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ option = input(f'Rock Paper or Scissors?') pass - pass""") + pass""" + ) skipped_mappings = [ SkippedMapping(SourceRange(2, 1, 2, 22), hedy.exceptions.ParseException), - SkippedMapping(SourceRange(3, 1, 3, 23), hedy.exceptions.InvalidSpaceException), + SkippedMapping( + SourceRange(3, 1, 3, 23), hedy.exceptions.InvalidSpaceException + ), ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=7 + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=7 ) def test_line_with_if_with_space_gives_invalid(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ name is Hedy - if name is 3 print 'leuk' else print 'stom'""") + if name is 3 print 'leuk' else print 'stom'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ name = 'Hedy' - pass""") + pass""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(2, 1, 2, 45), hedy.exceptions.InvalidSpaceException), + SkippedMapping( + SourceRange(2, 1, 2, 45), hedy.exceptions.InvalidSpaceException + ), ] self.multi_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings, - max_level=7) + code=code, expected=expected, skipped_mappings=skipped_mappings, max_level=7 + ) def test_pront_should_suggest_print(self): code = "pront 'Hedy is leuk!'" expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 23), hedy.exceptions.InvalidCommandException), + SkippedMapping( + SourceRange(1, 1, 1, 23), hedy.exceptions.InvalidCommandException + ), ] self.multi_level_tester( code=code, expected=expected, skipped_mappings=skipped_mappings, - extra_check_function=lambda c: str(c.arguments['guessed_command']) == 'print', + extra_check_function=lambda c: str(c.arguments["guessed_command"]) + == "print", ) def test_print_no_quotes(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print 'Hoi ik ben Hedy de Waarzegger print 'Ik kan voorspellen wie morgen de loterij wint!' - naam is ask 'Wie ben jij?'""") + naam is ask 'Wie ben jij?'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ pass print(f'Ik kan voorspellen wie morgen de loterij wint!') - naam = input(f'Wie ben jij?')""") + naam = input(f'Wie ben jij?')""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 37), hedy.exceptions.UnquotedTextException), + SkippedMapping( + SourceRange(1, 1, 1, 37), hedy.exceptions.UnquotedTextException + ), ] self.single_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings + code=code, expected=expected, skipped_mappings=skipped_mappings ) def test_print_quote_gives_exception(self): - code = textwrap.dedent("""\ - print 'what's your name?'""") + code = textwrap.dedent( + """\ + print 'what's your name?'""" + ) expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 27), hedy.exceptions.UnquotedTextException), + SkippedMapping( + SourceRange(1, 1, 1, 27), hedy.exceptions.UnquotedTextException + ), ] self.single_level_tester( - code=code, - expected=expected, - skipped_mappings=skipped_mappings + code=code, expected=expected, skipped_mappings=skipped_mappings ) def test_ask_with_quote(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ name is ask 'what's your name?' - print name""") + print name""" + ) self.multi_level_tester( code=code, exception=hedy.exceptions.UnquotedTextException, extra_check_function=lambda c: c.exception.error_location[0] == 1, - max_level=17 + max_level=17, ) def test_if_equality_print_backtick_text_gives_error(self): code = "if 1 is 1 print `yay!` else print `nay`" - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ if '1' == '1': pass else: - pass""") + pass""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(1, 11, 1, 23), hedy.exceptions.UnquotedTextException), - SkippedMapping(SourceRange(1, 29, 1, 40), hedy.exceptions.UnquotedTextException), + SkippedMapping( + SourceRange(1, 11, 1, 23), hedy.exceptions.UnquotedTextException + ), + SkippedMapping( + SourceRange(1, 29, 1, 40), hedy.exceptions.UnquotedTextException + ), ] self.multi_level_tester( - max_level=5, - code=code, - expected=expected, - skipped_mappings=skipped_mappings + max_level=5, code=code, expected=expected, skipped_mappings=skipped_mappings ) def test_if_fix_nl(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 5 als naam is 5 print 'leuk' - print 'minder leuk!'""") + print 'minder leuk!'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ naam = '5' if naam == '5': print(f'leuk') else: x__x__x__x = '5' - print(f'minder leuk!')""") + print(f'minder leuk!')""" + ) self.multi_level_tester( - max_level=5, - code=code, - lang='nl', - expected=expected, - translate=False + max_level=5, code=code, lang="nl", expected=expected, translate=False ) # @@ -986,10 +1192,13 @@ def test_if_fix_nl(self): # def test_if_pressed_x_is_letter_key(self): - code = textwrap.dedent("""\ - if x is pressed print 'it is a letter key' else print 'it is another letter key'""") + code = textwrap.dedent( + """\ + if x is pressed print 'it is a letter key' else print 'it is another letter key'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1005,16 +1214,20 @@ def test_if_pressed_x_is_letter_key(self): else: print(f'it is another letter key') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_x_is_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ x is a - if x is pressed print 'it is a letter key' else print 'it is another letter key'""") + if x is pressed print 'it is a letter key' else print 'it is another letter key'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ x = 'a' pygame_end = False while not pygame_end: @@ -1031,16 +1244,20 @@ def test_if_pressed_x_is_variable(self): else: print(f'it is another letter key') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_double_if_pressed(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed print 'first key' else print 'something else' - if y is pressed print 'second key' else print 'something else'""") + if y is pressed print 'second key' else print 'something else'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1072,16 +1289,20 @@ def test_double_if_pressed(self): else: print(f'something else') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_has_enter_after_pressed(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed - print 'it is a letter key' else print 'something else'""") + print 'it is a letter key' else print 'something else'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1097,15 +1318,19 @@ def test_if_pressed_has_enter_after_pressed(self): else: print(f'something else') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_1_is_number_key(self): - code = textwrap.dedent("""\ - if 1 is pressed print 'it is a number key' else print 'something else'""") + code = textwrap.dedent( + """\ + if 1 is pressed print 'it is a number key' else print 'something else'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1121,15 +1346,19 @@ def test_if_pressed_1_is_number_key(self): else: print(f'something else') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_with_trailing_spaces_after_key(self): - code = textwrap.dedent("""\ - if x is pressed print 'trailing spaces!' else print 'something else'""") + code = textwrap.dedent( + """\ + if x is pressed print 'trailing spaces!' else print 'something else'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1145,16 +1374,20 @@ def test_if_pressed_with_trailing_spaces_after_key(self): else: print(f'something else') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_has_enter_before_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed print 'x is pressed!' - else print 'x is not pressed!'""") + else print 'x is not pressed!'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1170,18 +1403,22 @@ def test_if_pressed_has_enter_before_else(self): else: print(f'x is not pressed!') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_has_enter_before_both_prints_and_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed print 'x is pressed!' else - print 'x is not pressed!'""") + print 'x is not pressed!'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1197,17 +1434,21 @@ def test_if_pressed_has_enter_before_both_prints_and_else(self): else: print(f'x is not pressed!') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_has_enter_before_first_print_and_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed print 'x is pressed!' - else print 'x is not pressed!'""") + else print 'x is not pressed!'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1223,17 +1464,21 @@ def test_if_pressed_has_enter_before_first_print_and_else(self): else: print(f'x is not pressed!') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_has_enter_before_second_print_and_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed print 'x is pressed!' else - print 'x is not pressed!'""") + print 'x is not pressed!'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1249,17 +1494,21 @@ def test_if_pressed_has_enter_before_second_print_and_else(self): else: print(f'x is not pressed!') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_has_enter_before_both_prints(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed print 'x is pressed!' - else print 'x is not pressed!'""") + else print 'x is not pressed!'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1275,15 +1524,19 @@ def test_if_pressed_has_enter_before_both_prints(self): else: print(f'x is not pressed!') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_else_with_turtle(self): - code = textwrap.dedent("""\ - if x is pressed forward 25 else turn 90""") + code = textwrap.dedent( + """\ + if x is pressed forward 25 else turn 90""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1305,22 +1558,26 @@ def test_if_pressed_else_with_turtle(self): 14, True) } break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=7 + max_level=7, ) def test_if_pressed_non_latin(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if ض is pressed print 'arabic' else print 'something else' if ש is pressed print 'hebrew' else print 'something else' - if й is pressed print 'russian' else print 'something else'""") + if й is pressed print 'russian' else print 'something else'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -1368,74 +1625,88 @@ def test_if_pressed_non_latin(self): else: print(f'something else') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_if_pressed_missing_else_gives_error(self): - code = textwrap.dedent("""\ - if x is pressed print 'hi!'""") + code = textwrap.dedent( + """\ + if x is pressed print 'hi!'""" + ) expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 28), hedy.exceptions.MissingElseForPressitException), + SkippedMapping( + SourceRange(1, 1, 1, 28), hedy.exceptions.MissingElseForPressitException + ), ] self.multi_level_tester( - max_level=7, - code=code, - expected=expected, - skipped_mappings=skipped_mappings + max_level=7, code=code, expected=expected, skipped_mappings=skipped_mappings ) def test_if_pressed_missing_else_gives_error_with_new_line(self): - code = textwrap.dedent("""\ - if x is pressed print 'hi!'\n\n""") + code = textwrap.dedent( + """\ + if x is pressed print 'hi!'\n\n""" + ) expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 28), hedy.exceptions.MissingElseForPressitException), + SkippedMapping( + SourceRange(1, 1, 1, 28), hedy.exceptions.MissingElseForPressitException + ), ] self.multi_level_tester( - max_level=7, - code=code, - expected=expected, - skipped_mappings=skipped_mappings + max_level=7, code=code, expected=expected, skipped_mappings=skipped_mappings ) # # button tests # def test_button(self): - code = textwrap.dedent("""\ - knop is button""") + code = textwrap.dedent( + """\ + knop is button""" + ) - expected = HedyTester.dedent(f"""\ - create_button('knop')""") + expected = HedyTester.dedent( + f"""\ + create_button('knop')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_two_buttons(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ knop1 is button - knop2 is button""") + knop2 is button""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ create_button('knop1') - create_button('knop2')""") + create_button('knop2')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_button_is_pressed_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ PRINT is button OTHER is button - if PRINT is pressed print 'The PRINT button got pressed!' else print 'other button was pressed!'""") + if PRINT is pressed print 'The PRINT button got pressed!' else print 'other button was pressed!'""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ create_button('PRINT') create_button('OTHER') pygame_end = False @@ -1453,17 +1724,21 @@ def test_if_button_is_pressed_print(self): else: print(f'other button was pressed!') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester(code=code, expected=expected, max_level=7) def test_source_map(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print 'Do you want a good (g) or bad (b) ending?' if g is pressed print 'They lived happily ever after ❤' - else print 'The prince was eaten by a hippopotamus 😭'""") + else print 'The prince was eaten by a hippopotamus 😭'""" + ) - expected_code = textwrap.dedent("""\ + expected_code = textwrap.dedent( + """\ print(f'Do you want a good (g) or bad (b) ending?') pygame_end = False while not pygame_end: @@ -1480,14 +1755,15 @@ def test_source_map(self): else: print(f'The prince was eaten by a hippopotamus 😭') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) expected_source_map = { - '1/1-1/50': '1/1-1/52', - '2/17-2/56': '12/7-12/48', - '3/6-3/54': '15/7-15/57', - '2/1-3/54': '2/1-17/34', - '1/1-3/55': '1/1-17/34' + "1/1-1/50": "1/1-1/52", + "2/17-2/56": "12/7-12/48", + "3/6-3/54": "15/7-15/57", + "2/1-3/54": "2/1-17/34", + "1/1-3/55": "1/1-17/34", } self.single_level_tester(code, expected=expected_code) diff --git a/tests/test_level/test_level_06.py b/tests/test_level/test_level_06.py index 384436417c1..d94d9aa9f4c 100644 --- a/tests/test_level/test_level_06.py +++ b/tests/test_level/test_level_06.py @@ -6,7 +6,7 @@ class TestsLevel6(HedyTester): level = 6 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -17,7 +17,7 @@ class TestsLevel6(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # ask tests @@ -29,15 +29,19 @@ def test_ask_equals(self): self.multi_level_tester(code=code, expected=expected, max_level=11) def test_ask_chained(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is ask 'What is a?' b is ask 'Are you sure a is ' a '?' - print a b""") + print a b""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = input(f'What is a?') b = input(f'Are you sure a is {a}?') - print(f'{a}{b}')""") + print(f'{a}{b}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -45,12 +49,14 @@ def test_ask_chained(self): # sleep tests # def test_sleep_with_calc(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1 * 2 + 3 - sleep n""") + sleep n""" + ) expected = HedyTester.dedent( - "n = int(1) * int(2) + int(3)", - HedyTester.sleep_command_transpiled("n")) + "n = int(1) * int(2) + int(3)", HedyTester.sleep_command_transpiled("n") + ) self.multi_level_tester(max_level=11, code=code, expected=expected) @@ -61,34 +67,23 @@ def test_assign_with_equals(self): code = "name = Hedy" expected = "name = 'Hedy'" - self.multi_level_tester( - max_level=11, - code=code, - expected=expected - ) + self.multi_level_tester(max_level=11, code=code, expected=expected) def test_assign_with_equals_no_space(self): code = "name=Hedy" expected = "name = 'Hedy'" - self.multi_level_tester( - max_level=11, - code=code, - expected=expected - ) + self.multi_level_tester(max_level=11, code=code, expected=expected) def test_assign_list_with_equals(self): code = "name = Hedy, Lamar" expected = "name = ['Hedy', 'Lamar']" - self.multi_level_tester( - max_level=11, - code=code, - expected=expected - ) + self.multi_level_tester(max_level=11, code=code, expected=expected) def test_assign_text_with_space(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 'Hello World' print a @@ -99,9 +94,11 @@ def test_assign_text_with_space(self): print a a = Hello World - print a""") + print a""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = '\\'Hello World\\'' print(f'{a}') a = '\\'Hello World\\'' @@ -109,189 +106,237 @@ def test_assign_text_with_space(self): a = 'Hello World' print(f'{a}') a = 'Hello World' - print(f'{a}')""") - - self.multi_level_tester( - max_level=11, - code=code, - expected=expected + print(f'{a}')""" ) - def test_assign_substract_negative_number(self): + self.multi_level_tester(max_level=11, code=code, expected=expected) - code = textwrap.dedent("""\ + def test_assign_substract_negative_number(self): + code = textwrap.dedent( + """\ n = -3-4 - print n""") + print n""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = int(-3) - int(4) - print(f'{n}')""") - - self.multi_level_tester( - max_level=11, - code=code, - expected=expected + print(f'{n}')""" ) + self.multi_level_tester(max_level=11, code=code, expected=expected) + # # if tests # def test_if_equality_linebreak_print(self): # line breaks after if-condition are allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy - print 'leuk'""") + print 'leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'leuk')""") + print(f'leuk')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_trailing_space_linebreak_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is James if naam is trailing_space - print 'shaken'""") + print 'shaken'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'trailing_space'): - print(f'shaken')""") + print(f'shaken')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_2_vars_equality_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ jouwkeuze is schaar computerkeuze is schaar - if computerkeuze is jouwkeuze print 'gelijkspel!'""") + if computerkeuze is jouwkeuze print 'gelijkspel!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ jouwkeuze = 'schaar' computerkeuze = 'schaar' if convert_numerals('Latin', computerkeuze) == convert_numerals('Latin', jouwkeuze): - print(f'gelijkspel!')""") + print(f'gelijkspel!')""" + ) - self.multi_level_tester(max_level=7, code=code, expected=expected, output='gelijkspel!') + self.multi_level_tester( + max_level=7, code=code, expected=expected, output="gelijkspel!" + ) def test_equality_arabic(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ nummer1 is ٢ nummer2 is 2 - if nummer1 is nummer2 print 'jahoor!'""") + if nummer1 is nummer2 print 'jahoor!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ nummer1 = '٢' nummer2 = '2' if convert_numerals('Latin', nummer1) == convert_numerals('Latin', nummer2): - print(f'jahoor!')""") + print(f'jahoor!')""" + ) - self.multi_level_tester(max_level=7, code=code, expected=expected, output='jahoor!') + self.multi_level_tester( + max_level=7, code=code, expected=expected, output="jahoor!" + ) @parameterized.expand(HedyTester.quotes) def test_if_equality_quoted_rhs_with_space(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam is James - if naam is {q}James Bond{q} print {q}shaken{q}""") + if naam is {q}James Bond{q} print {q}shaken{q}""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ naam = 'James' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'James Bond'): - print(f'shaken')""") + print(f'shaken')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) @parameterized.expand(HedyTester.quotes) def test_if_equality_quoted_rhs_with_spaces(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam is James - if naam is {q}Bond James Bond{q} print 'shaken'""") + if naam is {q}Bond James Bond{q} print 'shaken'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ naam = 'James' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Bond James Bond'): - print(f'shaken')""") + print(f'shaken')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_single_quoted_rhs_with_inner_double_quote(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ answer is no - if answer is 'He said "no"' print 'no'""") + if answer is 'He said "no"' print 'no'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ answer = 'no' if convert_numerals('Latin', answer) == convert_numerals('Latin', 'He said "no"'): - print(f'no')""") + print(f'no')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_equality_double_quoted_rhs_with_inner_single_quote(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ answer is no - if answer is "He said 'no'" print 'no'""") + if answer is "He said 'no'" print 'no'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ answer = 'no' if convert_numerals('Latin', answer) == convert_numerals('Latin', 'He said \\'no\\''): - print(f'no')""") + print(f'no')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_equality_promotes_int_to_string(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is test b is 15 - if a is b c is 1""") + if a is b c is 1""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = 'test' b = '15' if convert_numerals('Latin', a) == convert_numerals('Latin', b): - c = '1'""") + c = '1'""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_assign_calc(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ cmp is 1 test is 2 acu is 0 - if test is cmp acu is acu + 1""") + if test is cmp acu is acu + 1""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ cmp = '1' test = '2' acu = '0' if convert_numerals('Latin', test) == convert_numerals('Latin', cmp): - acu = int(acu) + int(1)""") + acu = int(acu) + int(1)""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_with_is(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy - if naam is Hedy print 'leuk'""") + if naam is Hedy print 'leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'leuk')""") + print(f'leuk')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_with_equals_sign(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy - if naam = Hedy print 'leuk'""") + if naam = Hedy print 'leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'leuk')""") + print(f'leuk')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) @@ -299,83 +344,105 @@ def test_if_equality_with_equals_sign(self): # if else tests # def test_if_equality_print_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy - if naam is Hedy print 'leuk' else print 'minder leuk'""") + if naam is Hedy print 'leuk' else print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_ask_equality_print_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ kleur is ask 'Wat is je lievelingskleur?' - if kleur is groen print 'mooi!' else print 'niet zo mooi'""") + if kleur is groen print 'mooi!' else print 'niet zo mooi'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ kleur = input(f'Wat is je lievelingskleur?') if convert_numerals('Latin', kleur) == convert_numerals('Latin', 'groen'): print(f'mooi!') else: - print(f'niet zo mooi')""") + print(f'niet zo mooi')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_else_followed_by_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ kleur is geel if kleur is groen antwoord is ok else antwoord is stom - print antwoord""") + print antwoord""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ kleur = 'geel' if convert_numerals('Latin', kleur) == convert_numerals('Latin', 'groen'): antwoord = 'ok' else: antwoord = 'stom' - print(f'{antwoord}')""") + print(f'{antwoord}')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_assign_else_assign(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ cmp is 1 test is 2 acu is 0 if test is cmp acu is acu + 1 else - acu is acu + 5""") + acu is acu + 5""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ cmp = '1' test = '2' acu = '0' if convert_numerals('Latin', test) == convert_numerals('Latin', cmp): acu = int(acu) + int(1) else: - acu = int(acu) + int(5)""") + acu = int(acu) + int(5)""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_with_negative_number(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord is -10 - if antwoord is -10 print 'Nice' else print 'Oh no'""") + if antwoord is -10 print 'Nice' else print 'Oh no'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ antwoord = '-10' if convert_numerals('Latin', antwoord) == convert_numerals('Latin', '-10'): print(f'Nice') else: - print(f'Oh no')""") + print(f'Oh no')""" + ) - self.multi_level_tester(code=code, expected=expected, output='Nice', max_level=7) + self.multi_level_tester( + code=code, expected=expected, output="Nice", max_level=7 + ) # Legal syntax: # @@ -391,121 +458,149 @@ def test_if_with_negative_number(self): def test_if_equality_print_linebreak_else_print(self): # line break before else is allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' - else print 'minder leuk'""") + else print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.multi_level_tester( max_level=7, code=code, expected=expected, - expected_commands=['is', 'if', 'else', 'print', 'print'] + expected_commands=["is", "if", "else", "print", "print"], ) def test_if_equality_linebreak_print_else_print(self): # line break after if-condition is allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy - print 'leuk' else print 'minder leuk'""") + print 'leuk' else print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_print_else_linebreak_print(self): # line break after else is allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' else - print 'minder leuk'""") + print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_linebreak_print_linebreak_else_print(self): # line breaks after if-condition and before else are allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' - else print 'minder leuk'""") + else print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_linebreak_print_linebreak_else_linebreak_print(self): # line breaks after if-condition, before else and after else are allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' else - print 'minder leuk'""") + print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_if_equality_linebreak_print_else_linebreak_print(self): # line breaks after if-condition and after else are allowed - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' else - print 'minder leuk'""") + print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) @parameterized.expand(HedyTester.quotes) def test_if_equality_quoted_rhs_with_space_else(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam is James - if naam is {q}James Bond{q} print {q}shaken{q} else print {q}biertje!{q}""") + if naam is {q}James Bond{q} print {q}shaken{q} else print {q}biertje!{q}""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ naam = 'James' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'James Bond'): print(f'shaken') else: - print(f'biertje!')""") + print(f'biertje!')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) @@ -515,52 +610,62 @@ def test_if_equality_quoted_rhs_with_space_else(self, q): def test_print_multiplication(self): code = "print 5 * 5" expected = "print(f'{int(5) * int(5)}')" - output = '25' + output = "25" - self.multi_level_tester(max_level=11, code=code, expected=expected, output=output) + self.multi_level_tester( + max_level=11, code=code, expected=expected, output=output + ) def test_print_addition(self): code = "print 5 + 5" expected = "print(f'{int(5) + int(5)}')" - output = '10' + output = "10" - self.multi_level_tester(max_level=11, code=code, expected=expected, output=output) + self.multi_level_tester( + max_level=11, code=code, expected=expected, output=output + ) def test_print_subtraction_without_text(self): code = "print 5 - 5" expected = "print(f'{int(5) - int(5)}')" - output = '0' + output = "0" - self.multi_level_tester(max_level=11, code=code, expected=expected, output=output) + self.multi_level_tester( + max_level=11, code=code, expected=expected, output=output + ) def test_print_subtraction_with_text(self): code = "print 'And the winner is ' 5 - 5" expected = "print(f'And the winner is {int(5) - int(5)}')" - output = 'And the winner is 0' + output = "And the winner is 0" self.multi_level_tester( - max_level=11, - code=code, - expected=expected, - output=output) + max_level=11, code=code, expected=expected, output=output + ) def test_print_nested_calcs(self): code = "print 5 * 5 * 5" expected = "print(f'{int(5) * int(5) * int(5)}')" - output = '125' + output = "125" - self.multi_level_tester(max_level=11, code=code, expected=expected, output=output) + self.multi_level_tester( + max_level=11, code=code, expected=expected, output=output + ) def test_assign_calc_print_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ nummer is 4 + 5 - print nummer""") + print nummer""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ nummer = int(4) + int(5) - print(f'{nummer}')""") + print(f'{nummer}')""" + ) - self.multi_level_tester(max_level=11, code=code, expected=expected, output='9') + self.multi_level_tester(max_level=11, code=code, expected=expected, output="9") def test_assign_calc_no_space(self): code = "nummer is 4+5" @@ -569,12 +674,16 @@ def test_assign_calc_no_space(self): self.multi_level_tester(max_level=11, code=code, expected=expected) def test_print_calc_with_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ var is 5 - print var + 5""") - expected = textwrap.dedent("""\ + print var + 5""" + ) + expected = textwrap.dedent( + """\ var = '5' - print(f'{int(var) + int(5)}')""") + print(f'{int(var) + int(5)}')""" + ) self.multi_level_tester(max_level=11, code=code, expected=expected) @@ -586,198 +695,226 @@ def test_assign_calc_precedes_quoted_string(self, operation): self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, + exception=hedy.exceptions.InvalidArgumentTypeException, ) - @parameterized.expand([ - ('*', '*', '16'), - ('/', '//', '4'), - ('+', '+', '10'), - ('-', '-', '6') - ]) + @parameterized.expand( + [("*", "*", "16"), ("/", "//", "4"), ("+", "+", "10"), ("-", "-", "6")] + ) def test_assign_calc_with_vars(self, op, transpiled_op, output): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ nummer is 8 nummertwee is 2 getal is nummer {op} nummertwee - print getal""") + print getal""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ nummer = '8' nummertwee = '2' getal = int(nummer) {transpiled_op} int(nummertwee) - print(f'{{getal}}')""") + print(f'{{getal}}')""" + ) - self.multi_level_tester(max_level=11, code=code, expected=expected, output=output) + self.multi_level_tester( + max_level=11, code=code, expected=expected, output=output + ) - @parameterized.expand([ - ('*', '*', '16'), - ('/', '//', '4'), - ('+', '+', '10'), - ('-', '-', '6') - ]) + @parameterized.expand( + [("*", "*", "16"), ("/", "//", "4"), ("+", "+", "10"), ("-", "-", "6")] + ) def test_print_calc_with_vars(self, op, transpiled_op, output): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ nummer is 8 nummertwee is 2 - print nummer {op} nummertwee""") + print nummer {op} nummertwee""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ nummer = '8' nummertwee = '2' - print(f'{{int(nummer) {transpiled_op} int(nummertwee)}}')""") + print(f'{{int(nummer) {transpiled_op} int(nummertwee)}}')""" + ) - self.multi_level_tester(max_level=11, code=code, expected=expected, output=output) + self.multi_level_tester( + max_level=11, code=code, expected=expected, output=output + ) - @parameterized.expand([ - ('*', '*', '16'), - ('/', '//', '4'), - ('+', '+', '10'), - ('-', '-', '6') - ]) + @parameterized.expand( + [("*", "*", "16"), ("/", "//", "4"), ("+", "+", "10"), ("-", "-", "6")] + ) def test_print_calc_with_vars_arabic(self, op, transpiled_op, output): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ nummer is ٨ nummertwee is ٢ - print nummer {op} nummertwee""") + print nummer {op} nummertwee""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ nummer = '٨' nummertwee = '٢' - print(f'{{int(nummer) {transpiled_op} int(nummertwee)}}')""") + print(f'{{int(nummer) {transpiled_op} int(nummertwee)}}')""" + ) - self.multi_level_tester(max_level=11, code=code, expected=expected, output=output) + self.multi_level_tester( + max_level=11, code=code, expected=expected, output=output + ) def test_print_calc_arabic_directly(self): # TODO: can also be generalized for other ops - code = textwrap.dedent(f"""\ - قول "٥ ضرب ٥ يساوي " ٥*٥""") + code = textwrap.dedent( + f"""\ + قول "٥ ضرب ٥ يساوي " ٥*٥""" + ) - expected = textwrap.dedent("""\ - print(f'٥ ضرب ٥ يساوي {convert_numerals("Arabic",int(5) * int(5))}')""") + expected = textwrap.dedent( + """\ + print(f'٥ ضرب ٥ يساوي {convert_numerals("Arabic",int(5) * int(5))}')""" + ) - output = '٥ ضرب ٥ يساوي ٢٥' + output = "٥ ضرب ٥ يساوي ٢٥" self.multi_level_tester( - max_level=11, - code=code, - expected=expected, - output=output, - lang='ar') + max_level=11, code=code, expected=expected, output=output, lang="ar" + ) def test_print_calc_arabic_directly_in_en(self): # TODO: can also be generalized for other ops - code = textwrap.dedent(f"""\ - print "nummers" ٥*٥""") + code = textwrap.dedent( + f"""\ + print "nummers" ٥*٥""" + ) - expected = textwrap.dedent("""\ - print(f'nummers{int(5) * int(5)}')""") + expected = textwrap.dedent( + """\ + print(f'nummers{int(5) * int(5)}')""" + ) self.multi_level_tester( - max_level=11, - code=code, - expected=expected, - translate=False) + max_level=11, code=code, expected=expected, translate=False + ) @parameterized.expand(HedyTester.arithmetic_operations) def test_calc_with_text_var_gives_type_error(self, operation): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is test - print a {operation} 2""") + print a {operation} 2""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) @parameterized.expand(HedyTester.arithmetic_operations) def test_calc_with_quoted_string_gives_type_error(self, operation): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is 1 - print a {operation} 'Test'""") + print a {operation} 'Test'""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) @parameterized.expand(HedyTester.arithmetic_operations) def test_calc_with_list_var_gives_type_error(self, operation): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is one, two - print a {operation} 2""") + print a {operation} 2""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) - @parameterized.expand(['1.5', '1,5']) + @parameterized.expand(["1.5", "1,5"]) def test_calculation_with_unsupported_float_gives_error(self, number): self.multi_level_tester( max_level=11, code=f"print {number} + 1", - exception=hedy.exceptions.UnsupportedFloatException + exception=hedy.exceptions.UnsupportedFloatException, ) def test_print_calc_chained_vars(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 5 b is a + 1 - print a + b""") + print a + b""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = '5' b = int(a) + int(1) - print(f'{int(a) + int(b)}')""") + print(f'{int(a) + int(b)}')""" + ) self.multi_level_tester( code=code, max_level=11, expected=expected, - expected_commands=['is', 'is', 'addition', 'print', 'addition'], - extra_check_function=lambda x: self.run_code(x) == "11" + expected_commands=["is", "is", "addition", "print", "addition"], + extra_check_function=lambda x: self.run_code(x) == "11", ) def test_type_reassignment_to_proper_type_valid(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is Hello a is 5 b is a + 1 - print a + b""") + print a + b""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = 'Hello' a = '5' b = int(a) + int(1) - print(f'{int(a) + int(b)}')""") + print(f'{int(a) + int(b)}')""" + ) self.multi_level_tester( code=code, max_level=11, expected=expected, - expected_commands=['is', 'is', 'is', 'addition', 'print', 'addition'], - extra_check_function=lambda x: self.run_code(x) == "11" + expected_commands=["is", "is", "is", "addition", "print", "addition"], + extra_check_function=lambda x: self.run_code(x) == "11", ) def test_type_reassignment_to_wrong_type_raises_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 5 a is test - print a + 2""") + print a + 2""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_cyclic_var_definition_gives_error(self): @@ -786,43 +923,50 @@ def test_cyclic_var_definition_gives_error(self): self.multi_level_tester( code=code, exception=hedy.exceptions.CyclicVariableDefinitionException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1 + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, ) # # combined tests # def test_if_calc_else_calc_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ keuzes is 1, 2, 3, 4, 5, regenworm punten is 0 worp is keuzes at random if worp is regenworm punten is punten + 5 else punten is punten + worp - print 'dat zijn dan ' punten""") + print 'dat zijn dan ' punten""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ keuzes = ['1', '2', '3', '4', '5', 'regenworm'] punten = '0'""", - HedyTester.list_access_transpiled('random.choice(keuzes)'), - "worp = random.choice(keuzes)", - """\ + HedyTester.list_access_transpiled("random.choice(keuzes)"), + "worp = random.choice(keuzes)", + """\ if convert_numerals('Latin', worp) == convert_numerals('Latin', 'regenworm'): punten = int(punten) + int(5) else: punten = int(punten) + int(worp) - print(f'dat zijn dan {punten}')""") + print(f'dat zijn dan {punten}')""", + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_consecutive_if_statements(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ names is Hedy, Lamar name is ask 'What is a name you like?' if name is Hedy print 'nice!' - if name in names print 'nice!'""") + if name in names print 'nice!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ names = ['Hedy', 'Lamar'] name = input(f'What is a name you like?') if convert_numerals('Latin', name) == convert_numerals('Latin', 'Hedy'): @@ -830,18 +974,22 @@ def test_consecutive_if_statements(self): else: x__x__x__x = '5' if name in names: - print(f'nice!')""") + print(f'nice!')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_consecutive_if_and_if_else_statements(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is ask 'hoe heet jij?' if naam is Hedy print 'leuk' if naam is Python print 'ook leuk' - else print 'minder leuk!'""") + else print 'minder leuk!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = input(f'hoe heet jij?') if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): print(f'leuk') @@ -850,36 +998,46 @@ def test_consecutive_if_and_if_else_statements(self): if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Python'): print(f'ook leuk') else: - print(f'minder leuk!')""") + print(f'minder leuk!')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_two_ifs_assign(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ order is fries if order is fries price is 5 drink is water - print drink""") + print drink""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ order = 'fries' if convert_numerals('Latin', order) == convert_numerals('Latin', 'fries'): price = '5' else: x__x__x__x = '5' drink = 'water' - print(f'{drink}')""") + print(f'{drink}')""" + ) - self.multi_level_tester(max_level=7, code=code, expected=expected, translate=False) + self.multi_level_tester( + max_level=7, code=code, expected=expected, translate=False + ) def test_consecutive_if_else_statements(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ names is Hedy, Lamar name is ask 'What is a name you like?' if name is Hedy print 'nice!' else print 'meh' - if name in names print 'nice!' else print 'meh'""") + if name in names print 'nice!' else print 'meh'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ names = ['Hedy', 'Lamar'] name = input(f'What is a name you like?') if convert_numerals('Latin', name) == convert_numerals('Latin', 'Hedy'): @@ -889,38 +1047,50 @@ def test_consecutive_if_else_statements(self): if name in names: print(f'nice!') else: - print(f'meh')""") + print(f'meh')""" + ) self.multi_level_tester(max_level=7, code=code, expected=expected) def test_print_single_number(self): - code = textwrap.dedent("""\ - print 5""") + code = textwrap.dedent( + """\ + print 5""" + ) - expected = textwrap.dedent("""\ - print(f'5')""") + expected = textwrap.dedent( + """\ + print(f'5')""" + ) self.multi_level_tester(max_level=6, code=code, expected=expected) def test_negative_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a = -3 b = a + 3 - print b""") - expected = textwrap.dedent("""\ + print b""" + ) + expected = textwrap.dedent( + """\ a = '-3' b = int(a) + int(3) - print(f'{b}')""") - self.multi_level_tester(code=code, expected=expected, output='0', max_level=11) + print(f'{b}')""" + ) + self.multi_level_tester(code=code, expected=expected, output="0", max_level=11) def test_turtle_with_expression(self): self.maxDiff = None - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ num = 10 turn num + 10 - forward 10 + num""") + forward 10 + num""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ num = '10' __trtl = int(num) + int(10) try: @@ -934,6 +1104,7 @@ def test_turtle_with_expression(self): except ValueError: raise Exception(f'While running your program the command forward received the value {__trtl} which is not allowed. Try changing the value to a number.') t.forward(min(600, __trtl) if __trtl > 0 else max(-600, __trtl)) - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) diff --git a/tests/test_level/test_level_07.py b/tests/test_level/test_level_07.py index f2fdb7c3758..459eec6b6ef 100644 --- a/tests/test_level/test_level_07.py +++ b/tests/test_level/test_level_07.py @@ -9,7 +9,7 @@ class TestsLevel7(HedyTester): level = 7 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -20,7 +20,7 @@ class TestsLevel7(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # repeat tests @@ -30,108 +30,133 @@ def test_repeat_turtle(self): expected = HedyTester.dedent( "for __i__ in range(int('3')):", - (HedyTester.forward_transpiled(100, self.level), ' ')) + (HedyTester.forward_transpiled(100, self.level), " "), + ) - self.single_level_tester(code=code, expected=expected, extra_check_function=self.is_turtle()) + self.single_level_tester( + code=code, expected=expected, extra_check_function=self.is_turtle() + ) def test_repeat_print(self): code = "repeat 5 times print 'me wants a cookie!'" - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for __i__ in range(int('5')): print(f'me wants a cookie!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) self.single_level_tester(code=code, expected=expected, output=output) def test_repeat_print_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 5 - repeat n times print 'me wants a cookie!'""") + repeat n times print 'me wants a cookie!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = '5' for __i__ in range(int(n)): print(f'me wants a cookie!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) self.single_level_tester(code=code, expected=expected, output=output) def test_repeat_print_undefined_variable_gives_error(self): code = "repeat n times print 'me wants a cookie!'" - self.single_level_tester(code=code, exception=hedy.exceptions.UndefinedVarException) + self.single_level_tester( + code=code, exception=hedy.exceptions.UndefinedVarException + ) def test_missing_body(self): code = "repeat 5 times" - self.multi_level_tester(code=code, - exception=hedy.exceptions.MissingInnerCommandException, - max_level=8) + self.multi_level_tester( + code=code, + exception=hedy.exceptions.MissingInnerCommandException, + max_level=8, + ) @parameterized.expand(HedyTester.quotes) def test_print_without_opening_quote_gives_error(self, q): code = f"print hedy 123{q}" self.multi_level_tester( - code, - max_level=17, - exception=hedy.exceptions.UnquotedTextException + code, max_level=17, exception=hedy.exceptions.UnquotedTextException ) @parameterized.expand(HedyTester.quotes) def test_print_without_closing_quote_gives_error(self, q): code = f"print {q}hedy 123" self.multi_level_tester( - code, - max_level=17, - exception=hedy.exceptions.UnquotedTextException + code, max_level=17, exception=hedy.exceptions.UnquotedTextException ) def test_repeat_with_string_variable_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 'test' - repeat n times print 'n'""") + repeat n times print 'n'""" + ) self.single_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) def test_repeat_with_list_variable_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1, 2, 3 - repeat n times print 'n'""") + repeat n times print 'n'""" + ) self.single_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) def test_repeat_with_missing_print_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ x is 3 - repeat 3 times x""") + repeat 3 times x""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ x = '3' - pass""") + pass""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(2, 1, 2, 17), hedy.exceptions.IncompleteRepeatException), + SkippedMapping( + SourceRange(2, 1, 2, 17), hedy.exceptions.IncompleteRepeatException + ), ] self.single_level_tester( @@ -141,16 +166,22 @@ def test_repeat_with_missing_print_gives_error(self): ) def test_repeat_with_missing_print_gives_lonely_text_exc(self): - code = textwrap.dedent("""\ - repeat 3 times 'n'""") + code = textwrap.dedent( + """\ + repeat 3 times 'n'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for __i__ in range(int('3')): pass - time.sleep(0.1)""") + time.sleep(0.1)""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(1, 16, 1, 19), hedy.exceptions.LonelyTextException), + SkippedMapping( + SourceRange(1, 16, 1, 19), hedy.exceptions.LonelyTextException + ), ] self.single_level_tester( @@ -160,13 +191,17 @@ def test_repeat_with_missing_print_gives_lonely_text_exc(self): ) def test_repeat_with_missing_times_gives_error(self): - code = textwrap.dedent("""\ - repeat 3 print 'n'""") + code = textwrap.dedent( + """\ + repeat 3 print 'n'""" + ) expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 1, 19), hedy.exceptions.IncompleteRepeatException), + SkippedMapping( + SourceRange(1, 1, 1, 19), hedy.exceptions.IncompleteRepeatException + ), ] self.single_level_tester( @@ -176,47 +211,82 @@ def test_repeat_with_missing_times_gives_error(self): ) def test_repeat_ask(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is ask 'How many times?' - repeat n times print 'n'""") + repeat n times print 'n'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = input(f'How many times?') for __i__ in range(int(n)): print(f'n') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester(code=code, expected=expected) - @parameterized.expand(['5', '𑁫', '५', '૫', '੫', '৫', '೫', '୫', '൫', '௫', - '౫', '၅', '༥', '᠕', '៥', '๕', '໕', '꧕', '٥', '۵']) + @parameterized.expand( + [ + "5", + "𑁫", + "५", + "૫", + "੫", + "৫", + "೫", + "୫", + "൫", + "௫", + "౫", + "၅", + "༥", + "᠕", + "៥", + "๕", + "໕", + "꧕", + "٥", + "۵", + ] + ) def test_repeat_with_all_numerals(self, number): code = textwrap.dedent(f"repeat {number} times print 'me wants a cookie!'") - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ for __i__ in range(int('{int(number)}')): print(f'me wants a cookie!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) self.single_level_tester(code=code, expected=expected, output=output) def test_repeat_over_9_times(self): - code = textwrap.dedent("""\ - repeat 10 times print 'me wants a cookie!'""") + code = textwrap.dedent( + """\ + repeat 10 times print 'me wants a cookie!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for __i__ in range(int('10')): print(f'me wants a cookie!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! @@ -226,61 +296,73 @@ def test_repeat_over_9_times(self): me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) self.single_level_tester( code=code, expected=expected, - expected_commands=['repeat', 'print'], - output=output) + expected_commands=["repeat", "print"], + output=output, + ) def test_repeat_with_variable_name_collision(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ i is hallo! repeat 5 times print 'me wants a cookie!' - print i""") + print i""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ i = 'hallo!' for __i__ in range(int('5')): print(f'me wants a cookie!') time.sleep(0.1) - print(f'{i}')""") + print(f'{i}')""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - hallo!""") + hallo!""" + ) self.single_level_tester( code=code, expected=expected, - expected_commands=['is', 'repeat', 'print', 'print'], - output=output) + expected_commands=["is", "repeat", "print", "print"], + output=output, + ) def test_repeat_if(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy - if naam is Hedy repeat 3 times print 'Hallo Hedy!'""") + if naam is Hedy repeat 3 times print 'Hallo Hedy!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): for __i__ in range(int('3')): print(f'Hallo Hedy!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - self.single_level_tester( - code=code, - expected=expected) + self.single_level_tester(code=code, expected=expected) def test_if_pressed_repeat(self): code = "if x is pressed repeat 5 times print 'doe het 5 keer!' else print 'iets anders'" - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -298,19 +380,21 @@ def test_if_pressed_repeat(self): else: print(f'iets anders') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) - self.single_level_tester( - code=code, - expected=expected) + self.single_level_tester(code=code, expected=expected) def test_if_pressed_multiple(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed print 'doe het 1 keer!' else print 'iets anders' if y is pressed print 'doe het 1 keer!' else print 'iets anders' - if z is pressed print 'doe het 1 keer!' else print 'iets anders'""") + if z is pressed print 'doe het 1 keer!' else print 'iets anders'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -358,20 +442,21 @@ def test_if_pressed_multiple(self): else: print(f'iets anders') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) - self.single_level_tester( - code=code, - expected=expected, - translate=False) + self.single_level_tester(code=code, expected=expected, translate=False) def test_repeat_if_pressed_multiple(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 3 times if x is pressed forward 15 else forward -15 repeat 3 times if y is pressed forward 15 else forward -15 - repeat 3 times if z is pressed forward 15 else forward -15""") + repeat 3 times if z is pressed forward 15 else forward -15""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ for __i__ in range(int('3')): pygame_end = False while not pygame_end: @@ -461,20 +546,21 @@ def test_repeat_if_pressed_multiple(self): time.sleep(0.1) break # End of PyGame Event Handler - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - self.single_level_tester( - code=code, - expected=expected, - translate=False) + self.single_level_tester(code=code, expected=expected, translate=False) def test_repeat_if_multiple(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ aan is ja repeat 3 times if aan is ja print 'Hedy is leuk!' - repeat 3 times if aan is ja print 'Hedy is leuk!'""") + repeat 3 times if aan is ja print 'Hedy is leuk!'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ aan = 'ja' for __i__ in range(int('3')): if convert_numerals('Latin', aan) == convert_numerals('Latin', 'ja'): @@ -485,40 +571,44 @@ def test_repeat_if_multiple(self): for __i__ in range(int('3')): if convert_numerals('Latin', aan) == convert_numerals('Latin', 'ja'): print(f'Hedy is leuk!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ Hedy is leuk! Hedy is leuk! Hedy is leuk! Hedy is leuk! Hedy is leuk! - Hedy is leuk!""") + Hedy is leuk!""" + ) - self.single_level_tester( - code=code, - expected=expected, - output=output) + self.single_level_tester(code=code, expected=expected, output=output) def test_source_map(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print 'The prince kept calling for help' repeat 5 times print 'Help!' - print 'Why is nobody helping me?'""") + print 'Why is nobody helping me?'""" + ) - expected_code = textwrap.dedent("""\ + expected_code = textwrap.dedent( + """\ print(f'The prince kept calling for help') for __i__ in range(int('5')): print(f'Help!') time.sleep(0.1) - print(f'Why is nobody helping me?')""") + print(f'Why is nobody helping me?')""" + ) expected_source_map = { - '1/1-1/41': '1/1-1/43', - '2/16-2/29': '3/3-3/18', - '2/1-2/29': '2/1-4/18', - '3/1-3/34': '5/1-5/36', - '1/1-3/35': '1/1-5/36' + "1/1-1/41": "1/1-1/43", + "2/16-2/29": "3/3-3/18", + "2/1-2/29": "2/1-4/18", + "3/1-3/34": "5/1-5/36", + "1/1-3/35": "1/1-5/36", } self.single_level_tester(code, expected=expected_code) diff --git a/tests/test_level/test_level_08.py b/tests/test_level/test_level_08.py index 4a39a2abc95..ba4d983b60d 100644 --- a/tests/test_level/test_level_08.py +++ b/tests/test_level/test_level_08.py @@ -10,7 +10,7 @@ class TestsLevel8(HedyTester): level = 8 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -21,23 +21,29 @@ class TestsLevel8(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # if command # def test_if_one_line(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord is 25 - if antwoord is 100 print 'goed zo' else print 'neenee'""") + if antwoord is 100 print 'goed zo' else print 'neenee'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ antwoord = '25' - pass""") + pass""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(2, 1, 2, 55), hedy.exceptions.WrongLevelException), + SkippedMapping( + SourceRange(2, 1, 2, 55), hedy.exceptions.WrongLevelException + ), ] # one line if's are no longer allowed @@ -49,378 +55,479 @@ def test_if_one_line(self): ) def test_if_no_indentation(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord is ask Hoeveel is 10 keer tien? if antwoord is 100 - print 'goed zo'""") + print 'goed zo'""" + ) # gives the right exception for all levels even though it misses brackets # because the indent check happens before parsing - self.multi_level_tester(code=code, exception=hedy.exceptions.NoIndentationException) + self.multi_level_tester( + code=code, exception=hedy.exceptions.NoIndentationException + ) def test_if_equality_with_is(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy - print 'leuk'""") + print 'leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'leuk')""") + print(f'leuk')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_equality_with_equals_sign(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam = Hedy - print 'leuk'""") + print 'leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'leuk')""") + print(f'leuk')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_equality_trailing_space_linebreak_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is James if naam is trailing_space - print 'shaken'""") + print 'shaken'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'trailing_space'): - print(f'shaken')""") + print(f'shaken')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_equality_unquoted_rhs_with_space(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is James if naam is James Bond - print 'shaken'""") + print 'shaken'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'James Bond'): - print(f'shaken')""") + print(f'shaken')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) - def test_if_equality_unquoted_rhs_with_space_and_trailing_space_linebreak_print(self): - code = textwrap.dedent("""\ + def test_if_equality_unquoted_rhs_with_space_and_trailing_space_linebreak_print( + self, + ): + code = textwrap.dedent( + """\ naam is James if naam is trailing space - print 'shaken'""") + print 'shaken'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'trailing space'): - print(f'shaken')""") + print(f'shaken')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @parameterized.expand(HedyTester.quotes) def test_if_equality_quoted_rhs_with_space(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam is James if naam is {q}James Bond{q} - print {q}shaken{q}""") + print {q}shaken{q}""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ naam = 'James' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'James Bond'): - print(f'shaken')""") + print(f'shaken')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @parameterized.expand(HedyTester.quotes) def test_if_equality_quoted_rhs_with_spaces(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam is James if naam is {q}Bond James Bond{q} - print 'shaken'""") + print 'shaken'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ naam = 'James' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Bond James Bond'): - print(f'shaken')""") + print(f'shaken')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_equality_single_quoted_rhs_with_inner_double_quote(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ answer is no if answer is 'He said "no"' - print 'no'""") + print 'no'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ answer = 'no' if convert_numerals('Latin', answer) == convert_numerals('Latin', 'He said "no"'): - print(f'no')""") + print(f'no')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_equality_double_quoted_rhs_with_inner_single_quote(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ answer is no if answer is "He said 'no'" - print 'no'""") + print 'no'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ answer = 'no' if convert_numerals('Latin', answer) == convert_numerals('Latin', 'He said \\'no\\''): - print(f'no')""") + print(f'no')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_2_vars_equality_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ jouwkeuze is schaar computerkeuze is schaar if computerkeuze is jouwkeuze - print 'gelijkspel!'""") + print 'gelijkspel!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ jouwkeuze = 'schaar' computerkeuze = 'schaar' if convert_numerals('Latin', computerkeuze) == convert_numerals('Latin', jouwkeuze): - print(f'gelijkspel!')""") + print(f'gelijkspel!')""" + ) - self.multi_level_tester(max_level=11, code=code, expected=expected, output='gelijkspel!') + self.multi_level_tester( + max_level=11, code=code, expected=expected, output="gelijkspel!" + ) def test_if_in_list_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ items is red, green selected is red if selected in items - print 'found!'""") + print 'found!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ items = ['red', 'green'] selected = 'red' if selected in items: - print(f'found!')""") + print(f'found!')""" + ) self.multi_level_tester( max_level=11, code=code, expected=expected, - output='found!', - expected_commands=['is', 'is', 'if', 'in', 'print'] + output="found!", + expected_commands=["is", "is", "if", "in", "print"], ) def test_if_equality_assign_calc(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ cmp is 1 test is 2 acu is 0 if test is cmp - acu is acu + 1""") + acu is acu + 1""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ cmp = '1' test = '2' acu = '0' if convert_numerals('Latin', test) == convert_numerals('Latin', cmp): - acu = int(acu) + int(1)""") + acu = int(acu) + int(1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_in_undefined_list_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ selected is 5 if selected in items - print 'found!'""") + print 'found!'""" + ) - self.multi_level_tester(code=code, exception=hedy.exceptions.UndefinedVarException, max_level=16) + self.multi_level_tester( + code=code, exception=hedy.exceptions.UndefinedVarException, max_level=16 + ) def test_equality_promotes_int_to_string(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is test b is 15 if a is b - c is 1""") + c is 1""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = 'test' b = '15' if convert_numerals('Latin', a) == convert_numerals('Latin', b): - c = '1'""") + c = '1'""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_equality_with_lists_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ m is 1, 2 n is 1, 2 if m is n - print 'success!'""") + print 'success!'""" + ) # FH Mar 2023: waarom is dit fout? self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_if_in_list_with_string_var_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ items is red if red in items - print 'found!'""") + print 'found!'""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_if_in_list_with_input_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ items is ask 'What are the items?' if red in items - print 'found!'""") + print 'found!'""" + ) self.multi_level_tester( max_level=16, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_equality_with_list_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is 5, 6, 7 if red is color - print 'success!'""") + print 'success!'""" + ) self.multi_level_tester( max_level=11, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_if_with_negative_number(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord = -10 if antwoord is -10 - print 'Nice'""") + print 'Nice'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ antwoord = '-10' if convert_numerals('Latin', antwoord) == convert_numerals('Latin', '-10'): - print(f'Nice')""") + print(f'Nice')""" + ) - self.multi_level_tester(code=code, expected=expected, output='Nice', max_level=11) + self.multi_level_tester( + code=code, expected=expected, output="Nice", max_level=11 + ) # # if else tests # def test_if_else_no_indentation(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord is ask Hoeveel is 10 keer tien? if antwoord is 100 print 'goed zo' else - print 'bah slecht'""") + print 'bah slecht'""" + ) # gives the right exception for all levels even though it misses brackets # because the indent check happens before parsing - self.multi_level_tester(code=code, exception=hedy.exceptions.NoIndentationException) + self.multi_level_tester( + code=code, exception=hedy.exceptions.NoIndentationException + ) def test_if_equality_print_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is Hedy if naam is Hedy print 'leuk' else - print 'minder leuk'""") + print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): print(f'leuk') else: - print(f'minder leuk')""") + print(f'minder leuk')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_equality_assign_else_assign(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 5 if a is 1 x is 2 else - x is 222""") - expected = textwrap.dedent("""\ + x is 222""" + ) + expected = textwrap.dedent( + """\ a = '5' if convert_numerals('Latin', a) == convert_numerals('Latin', '1'): x = '2' else: - x = '222'""") + x = '222'""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_else_followed_by_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ kleur is geel if kleur is groen antwoord is ok else antwoord is stom - print antwoord""") + print antwoord""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ kleur = 'geel' if convert_numerals('Latin', kleur) == convert_numerals('Latin', 'groen'): antwoord = 'ok' else: antwoord = 'stom' - print(f'{antwoord}')""") + print(f'{antwoord}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_else_trailing_space_after_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 1 if a is 1 print a else - print 'nee'""") + print 'nee'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = '1' if convert_numerals('Latin', a) == convert_numerals('Latin', '1'): print(f'{a}') else: - print(f'nee')""") + print(f'nee')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_list_assignment_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ people is mom, dad, Emma, Sophie dishwasher is people at random if dishwasher is Sophie print 'too bad I have to do the dishes' else - print 'luckily no dishes because' dishwasher 'is already washing up'""") + print 'luckily no dishes because' dishwasher 'is already washing up'""" + ) expected = HedyTester.dedent( "people = ['mom', 'dad', 'Emma', 'Sophie']", - HedyTester.list_access_transpiled('random.choice(people)'), + HedyTester.list_access_transpiled("random.choice(people)"), """\ dishwasher = random.choice(people) if convert_numerals('Latin', dishwasher) == convert_numerals('Latin', 'Sophie'): print(f'too bad I have to do the dishes') else: - print(f'luckily no dishes because{dishwasher}is already washing up')""") + print(f'luckily no dishes because{dishwasher}is already washing up')""", + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -433,59 +540,76 @@ def test_print_line_with_spaces_works(self): code=code, expected=expected, expected_commands=expected_commands, - max_level=11) + max_level=11, + ) def test_if_empty_start_line_with_whitespace_else_print(self): code = " \n" - code += textwrap.dedent("""\ + code += textwrap.dedent( + """\ if 1 is 2 print 'nice!' else - print 'pizza is better'""") + print 'pizza is better'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ if convert_numerals('Latin', '1') == convert_numerals('Latin', '2'): print(f'nice!') else: - print(f'pizza is better')""") + print(f'pizza is better')""" + ) - self.multi_level_tester(code=code, expected=expected, max_level=11, translate=False) + self.multi_level_tester( + code=code, expected=expected, max_level=11, translate=False + ) def test_if_empty_middle_line_with_whitespace_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if 1 is 2 - print 'nice!'""") + print 'nice!'""" + ) code += "\n \n" - code += textwrap.dedent("""\ + code += textwrap.dedent( + """\ else - print 'pizza is better'""") + print 'pizza is better'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ if convert_numerals('Latin', '1') == convert_numerals('Latin', '2'): print(f'nice!') else: - print(f'pizza is better')""") + print(f'pizza is better')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_else_with_multiple_lines(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord is ask 'Hoeveel is 10 plus 10?' if antwoord is 20 print 'Goedzo!' print 'Het antwoord was inderdaad ' antwoord else print 'Foutje' - print 'Het antwoord moest zijn ' antwoord""") + print 'Het antwoord moest zijn ' antwoord""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ antwoord = input(f'Hoeveel is 10 plus 10?') if convert_numerals('Latin', antwoord) == convert_numerals('Latin', '20'): print(f'Goedzo!') print(f'Het antwoord was inderdaad {antwoord}') else: print(f'Foutje') - print(f'Het antwoord moest zijn {antwoord}')""") + print(f'Het antwoord moest zijn {antwoord}')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -493,269 +617,371 @@ def test_if_else_with_multiple_lines(self): # repeat command # def test_repeat_no_indentation(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 3 times - print 'hooray!'""") + print 'hooray!'""" + ) - self.multi_level_tester(code=code, exception=hedy.exceptions.NoIndentationException) + self.multi_level_tester( + code=code, exception=hedy.exceptions.NoIndentationException + ) def test_repeat_repair_too_few_indents(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 5 times print('repair') - print('me')""") + print('me')""" + ) - fixed_code = textwrap.dedent("""\ + fixed_code = textwrap.dedent( + """\ repeat 5 times print('repair') - print('me')""") + print('me')""" + ) self.multi_level_tester( code=code, exception=hedy.exceptions.NoIndentationException, - extra_check_function=(lambda x: x.exception.fixed_code == fixed_code) + extra_check_function=(lambda x: x.exception.fixed_code == fixed_code), ) def test_repeat_repair_too_many_indents(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 5 times print('repair') - print('me')""") - fixed_code = textwrap.dedent("""\ + print('me')""" + ) + fixed_code = textwrap.dedent( + """\ repeat 5 times print('repair') - print('me')""") + print('me')""" + ) self.multi_level_tester( code=code, exception=hedy.exceptions.IndentationException, - extra_check_function=(lambda x: x.exception.fixed_code == fixed_code) + extra_check_function=(lambda x: x.exception.fixed_code == fixed_code), ) def test_unexpected_indent(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print('repair') - print('me')""") + print('me')""" + ) self.multi_level_tester( - code=code, - exception=hedy.exceptions.IndentationException + code=code, exception=hedy.exceptions.IndentationException ) def test_repeat_turtle(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 3 times - forward 100""") + forward 100""" + ) expected = HedyTester.dedent( "for i in range(int('3')):", - (HedyTester.forward_transpiled(100, self.level), ' ')) + (HedyTester.forward_transpiled(100, self.level), " "), + ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - max_level=11 + max_level=11, ) def test_repeat_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 5 times - print 'koekoek'""") + print 'koekoek'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for i in range(int('5')): print(f'koekoek') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_repeat_print_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 5 repeat n times - print 'me wants a cookie!'""") + print 'me wants a cookie!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = '5' for i in range(int(n)): print(f'me wants a cookie!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) - self.multi_level_tester(code=code, expected=expected, output=output, max_level=11) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=11 + ) def test_repeat_arabic(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat ٥ times - print 'koekoek'""") + print 'koekoek'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for i in range(int('5')): print(f'koekoek') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_repeat_with_arabic_variable_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is ٥ repeat ٥ times - print 'me wants a cookie!'""") + print 'me wants a cookie!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = '٥' for i in range(int('5')): print(f'me wants a cookie!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) - self.multi_level_tester(code=code, expected=expected, output=output, max_level=11) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=11 + ) def test_repeat_with_non_latin_variable_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ állatok is 5 repeat állatok times - print 'me wants a cookie!'""") + print 'me wants a cookie!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ állatok = '5' for i in range(int(állatok)): print(f'me wants a cookie!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) - self.multi_level_tester(code=code, expected=expected, output=output, max_level=11) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=11 + ) def test_repeat_undefined_variable_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat n times - print 'me wants a cookie!'""") + print 'me wants a cookie!'""" + ) self.multi_level_tester( - code=code, - exception=hedy.exceptions.UndefinedVarException, - max_level=17) + code=code, exception=hedy.exceptions.UndefinedVarException, max_level=17 + ) # issue 297 def test_repeat_print_assign_addition(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ count is 1 repeat 12 times print count ' times 12 is ' count * 12 - count is count + 1""") + count is count + 1""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ count = '1' for i in range(int('12')): print(f'{count} times 12 is {int(count) * int(12)}') count = int(count) + int(1) - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_repeat_with_comment(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 5 times #This should be ignored print 'koekoek' - print 'koekoek'""") + print 'koekoek'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for i in range(int('5')): print(f'koekoek') print(f'koekoek') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_repeat_with_string_variable_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 'test' repeat n times - print 'n'""") + print 'n'""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, exception=hedy.exceptions.InvalidArgumentTypeException, - max_level=17) + max_level=17, + ) def test_repeat_with_list_variable_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1, 2, 3 repeat n times - print 'n'""") + print 'n'""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, exception=hedy.exceptions.InvalidArgumentTypeException, - max_level=15) + max_level=15, + ) def test_repeat_ask(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is ask 'How many times?' repeat n times - print 'n'""") + print 'n'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = input(f'How many times?') for i in range(int(n)): print(f'n') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) - @parameterized.expand(['5', '𑁫', '५', '૫', '੫', '৫', '೫', '୫', '൫', '௫', - '౫', '၅', '༥', '᠕', '៥', '๕', '໕', '꧕', '٥', '۵']) + @parameterized.expand( + [ + "5", + "𑁫", + "५", + "૫", + "੫", + "৫", + "೫", + "୫", + "൫", + "௫", + "౫", + "၅", + "༥", + "᠕", + "៥", + "๕", + "໕", + "꧕", + "٥", + "۵", + ] + ) def test_repeat_with_all_numerals(self, number): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ repeat {number} times - print 'me wants a cookie!'""") + print 'me wants a cookie!'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ for i in range(int('{int(number)}')): print(f'me wants a cookie!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) - self.multi_level_tester(code=code, expected=expected, output=output, max_level=11) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=11 + ) def test_repeat_over_9_times(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 10 times - print 'me wants a cookie!'""") + print 'me wants a cookie!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for i in range(int('10')): print(f'me wants a cookie!') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! @@ -765,44 +991,51 @@ def test_repeat_over_9_times(self): me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) self.multi_level_tester( code=code, expected=expected, - expected_commands=['repeat', 'print'], + expected_commands=["repeat", "print"], output=output, - max_level=11 + max_level=11, ) def test_repeat_with_variable_name_collision(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ i is hallo! repeat 5 times print 'me wants a cookie!' - print i""") + print i""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ i = 'hallo!' for _i in range(int('5')): print(f'me wants a cookie!') time.sleep(0.1) - print(f'{i}')""") + print(f'{i}')""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - hallo!""") + hallo!""" + ) self.multi_level_tester( code=code, expected=expected, - expected_commands=['is', 'repeat', 'print', 'print'], + expected_commands=["is", "repeat", "print", "print"], output=output, - max_level=11 + max_level=11, ) # @@ -811,37 +1044,48 @@ def test_repeat_with_variable_name_collision(self): # issue 902 def test_repeat_if_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print 'kassabon' prijs is 0 repeat 7 times # TEST ingredient is ask 'wat wil je kopen?' if ingredient is appel prijs is prijs + 1 - print 'Dat is in totaal ' prijs ' euro.'""") + print 'Dat is in totaal ' prijs ' euro.'""" + ) - self.single_level_tester(code=code, exception=hedy.exceptions.LockedLanguageFeatureException) + self.single_level_tester( + code=code, exception=hedy.exceptions.LockedLanguageFeatureException + ) def test_if_repeat_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ kleur is groen if kleur is groen repeat 3 times - print 'mooi'""") + print 'mooi'""" + ) - self.single_level_tester(code=code, exception=hedy.exceptions.LockedLanguageFeatureException) + self.single_level_tester( + code=code, exception=hedy.exceptions.LockedLanguageFeatureException + ) # # if pressed tests # def test_if_pressed_x_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed print 'it is a letter key' else - print 'other key'""") - expected = HedyTester.dedent("""\ + print 'other key'""" + ) + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -857,11 +1101,13 @@ def test_if_pressed_x_print(self): # End of PyGame Event Handler else: print(f'other key') - break""") + break""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_double_if_pressed(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed print 'first key' else @@ -869,9 +1115,11 @@ def test_double_if_pressed(self): if y is pressed print 'second key' else - print 'other key'""") + print 'other key'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -903,19 +1151,23 @@ def test_double_if_pressed(self): # End of PyGame Event Handler else: print(f'other key') - break""") + break""" + ) self.maxDiff = None self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_pressed_is_number_key_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if 1 is pressed print 'it is a number key' else - print 'it is something else'""") + print 'it is something else'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -931,12 +1183,14 @@ def test_if_pressed_is_number_key_print(self): # End of PyGame Event Handler else: print(f'it is something else') - break""") + break""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_pressed_command_in_between(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if a is pressed print 'A is pressed' else @@ -945,9 +1199,11 @@ def test_if_pressed_command_in_between(self): if b is pressed print 'B is pressed' else - print 'other'""") + print 'other'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -980,52 +1236,64 @@ def test_if_pressed_command_in_between(self): # End of PyGame Event Handler else: print(f'other') - break""") + break""" + ) self.maxDiff = None self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_pressed_missing_else_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed - print 'missing else!'""") + print 'missing else!'""" + ) expected = "pass" skipped_mappings = [ - SkippedMapping(SourceRange(1, 1, 2, 34), hedy.exceptions.MissingElseForPressitException), + SkippedMapping( + SourceRange(1, 1, 2, 34), hedy.exceptions.MissingElseForPressitException + ), ] self.multi_level_tester( code=code, expected=expected, skipped_mappings=skipped_mappings, - max_level=14 + max_level=14, ) def test_if_no_indent_after_pressed_and_else_gives_noindent_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed print 'no indent!' else - print 'no indent again!'""") + print 'no indent again!'""" + ) # gives the right exception for all levels even though it misses brackets # because the indent check happens before parsing - self.multi_level_tester(code=code, exception=hedy.exceptions.NoIndentationException) + self.multi_level_tester( + code=code, exception=hedy.exceptions.NoIndentationException + ) # # button tests # def test_if_button_is_pressed_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ PRINT is button if PRINT is pressed print 'The button got pressed!' else - print 'other is pressed'""") + print 'other is pressed'""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ create_button('PRINT') pygame_end = False while not pygame_end: @@ -1042,19 +1310,23 @@ def test_if_button_is_pressed_print(self): # End of PyGame Event Handler else: print(f'other is pressed') - break""") + break""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_button_is_pressed_make_button(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ BUTTON1 is button if BUTTON1 is pressed BUTTON2 is button else - print 'something else'""") + print 'something else'""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ create_button('BUTTON1') pygame_end = False while not pygame_end: @@ -1071,20 +1343,24 @@ def test_if_button_is_pressed_make_button(self): # End of PyGame Event Handler else: print(f'something else') - break""") + break""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_button_is_pressed_print_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ PRINT is button PRINT2 is button if PRINT is pressed print 'The button got pressed!' else - print 'oof :('""") + print 'oof :('""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ create_button('PRINT') create_button('PRINT2') pygame_end = False @@ -1102,12 +1378,14 @@ def test_if_button_is_pressed_print_else_print(self): # End of PyGame Event Handler else: print(f'oof :(') - break""") + break""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_source_map(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print 'Welcome to Restaurant Chez Hedy!' people = ask 'How many people will be joining us today?' print 'Great!' @@ -1115,9 +1393,11 @@ def test_source_map(self): food = ask 'What would you like to order?' print food print 'Thank you for ordering!' - print 'Enjoy your meal!'""") + print 'Enjoy your meal!'""" + ) - expected_code = textwrap.dedent("""\ + expected_code = textwrap.dedent( + """\ print(f'Welcome to Restaurant Chez Hedy!') people = input(f'How many people will be joining us today?') print(f'Great!') @@ -1126,22 +1406,23 @@ def test_source_map(self): print(f'{food}') time.sleep(0.1) print(f'Thank you for ordering!') - print(f'Enjoy your meal!')""") + print(f'Enjoy your meal!')""" + ) expected_source_map = { - '1/1-1/41': '1/1-1/43', - '2/1-2/7': '2/1-2/7', - '2/1-2/57': '2/1-2/61', - '3/1-3/15': '3/1-3/17', - '4/8-4/14': '2/27-2/33', - '5/5-5/9': '5/3-5/7', - '5/5-5/47': '5/3-5/49', - '6/11-6/15': '6/12-6/16', - '6/5-6/15': '6/3-6/19', - '4/1-6/24': '4/1-7/18', - '7/1-7/32': '8/1-8/34', - '8/1-8/25': '9/1-9/27', - '1/1-8/26': '1/1-9/27' + "1/1-1/41": "1/1-1/43", + "2/1-2/7": "2/1-2/7", + "2/1-2/57": "2/1-2/61", + "3/1-3/15": "3/1-3/17", + "4/8-4/14": "2/27-2/33", + "5/5-5/9": "5/3-5/7", + "5/5-5/47": "5/3-5/49", + "6/11-6/15": "6/12-6/16", + "6/5-6/15": "6/3-6/19", + "4/1-6/24": "4/1-7/18", + "7/1-7/32": "8/1-8/34", + "8/1-8/25": "9/1-9/27", + "1/1-8/26": "1/1-9/27", } self.single_level_tester(code, expected=expected_code) diff --git a/tests/test_level/test_level_09.py b/tests/test_level/test_level_09.py index 6b55e4a0685..96332dd95e6 100644 --- a/tests/test_level/test_level_09.py +++ b/tests/test_level/test_level_09.py @@ -5,7 +5,7 @@ class TestsLevel9(HedyTester): level = 9 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -16,30 +16,35 @@ class TestsLevel9(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # if nesting # def test_if_nested_in_if(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1 m is 2 if n is 1 if m is 2 - print 'great!'""") + print 'great!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = '1' m = '2' if convert_numerals('Latin', n) == convert_numerals('Latin', '1'): if convert_numerals('Latin', m) == convert_numerals('Latin', '2'): - print(f'great!')""") + print(f'great!')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_ifs_nested_in_if_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1 m is 2 if n is 1 @@ -47,9 +52,11 @@ def test_ifs_nested_in_if_else(self): print 'great!' else if m is 3 - print 'awesome'""") + print 'awesome'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = '1' m = '2' if convert_numerals('Latin', n) == convert_numerals('Latin', '1'): @@ -57,33 +64,39 @@ def test_ifs_nested_in_if_else(self): print(f'great!') else: if convert_numerals('Latin', m) == convert_numerals('Latin', '3'): - print(f'awesome')""") + print(f'awesome')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_else_nested_in_if(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1 m is 2 if n is 1 if m is 2 print 'great!' else - print 'awesome'""") + print 'awesome'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = '1' m = '2' if convert_numerals('Latin', n) == convert_numerals('Latin', '1'): if convert_numerals('Latin', m) == convert_numerals('Latin', '2'): print(f'great!') else: - print(f'awesome')""") + print(f'awesome')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_else_statements_nested_in_if_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1 m is 2 if n is 1 @@ -95,9 +108,11 @@ def test_if_else_statements_nested_in_if_else(self): if m is 3 print 'awesome!' else - print 'amazing!'""") + print 'amazing!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = '1' m = '2' if convert_numerals('Latin', n) == convert_numerals('Latin', '1'): @@ -109,7 +124,8 @@ def test_if_else_statements_nested_in_if_else(self): if convert_numerals('Latin', m) == convert_numerals('Latin', '3'): print(f'awesome!') else: - print(f'amazing!')""") + print(f'amazing!')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -117,16 +133,20 @@ def test_if_else_statements_nested_in_if_else(self): # repeat nesting # def test_repeat_nested_in_repeat(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 2 times repeat 3 times - print 'hello'""") + print 'hello'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for i in range(int('2')): for i in range(int('3')): print(f'hello') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -134,80 +154,97 @@ def test_repeat_nested_in_repeat(self): # if and repeat nesting # def test_if_nested_in_repeat(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ prijs is 0 repeat 7 times ingredient is ask 'wat wil je kopen?' if ingredient is appel prijs is prijs + 1 - print 'Dat is in totaal ' prijs ' euro.'""") + print 'Dat is in totaal ' prijs ' euro.'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ prijs = '0' for i in range(int('7')): ingredient = input(f'wat wil je kopen?') if convert_numerals('Latin', ingredient) == convert_numerals('Latin', 'appel'): prijs = int(prijs) + int(1) time.sleep(0.1) - print(f'Dat is in totaal {prijs} euro.')""") + print(f'Dat is in totaal {prijs} euro.')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_if_nested_in_repeat_with_comment(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ prijs is 0 repeat 7 times # comment ingredient is ask 'wat wil je kopen?' if ingredient is appel # another comment prijs is prijs + 1 - print 'Dat is in totaal ' prijs ' euro.'""") + print 'Dat is in totaal ' prijs ' euro.'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ prijs = '0' for i in range(int('7')): ingredient = input(f'wat wil je kopen?') if convert_numerals('Latin', ingredient) == convert_numerals('Latin', 'appel'): prijs = int(prijs) + int(1) time.sleep(0.1) - print(f'Dat is in totaal {prijs} euro.')""") + print(f'Dat is in totaal {prijs} euro.')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_repeat_nested_in_if(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ kleur is groen if kleur is groen repeat 3 times - print 'mooi'""") + print 'mooi'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ kleur = 'groen' if convert_numerals('Latin', kleur) == convert_numerals('Latin', 'groen'): for i in range(int('3')): print(f'mooi') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( code=code, expected=expected, max_level=11, - expected_commands=['is', 'if', 'repeat', 'print']) + expected_commands=["is", "if", "repeat", "print"], + ) def test_if_else_nested_in_repeat(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 5 times if antwoord2 is 10 print 'Goedzo' else - print 'lalala'""") + print 'lalala'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for i in range(int('5')): if convert_numerals('Latin', 'antwoord2') == convert_numerals('Latin', '10'): print(f'Goedzo') else: print(f'lalala') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -216,14 +253,17 @@ def test_if_else_nested_in_repeat(self): # def test_if_pressed_repeat(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed repeat 5 times print 'doe het 5 keer!' else - print '1 keertje'""") + print '1 keertje'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ pygame_end = False while not pygame_end: pygame.display.update() @@ -241,7 +281,8 @@ def test_if_pressed_repeat(self): # End of PyGame Event Handler else: print(f'1 keertje') - break""") + break""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) @@ -250,15 +291,18 @@ def test_if_pressed_repeat(self): # def test_if_button_is_pressed_print_in_repeat(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ button1 is button repeat 3 times if button1 is pressed print 'wow' else - print 'nah'""") + print 'nah'""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ create_button('button1') for i in range(int('3')): pygame_end = False @@ -277,30 +321,33 @@ def test_if_button_is_pressed_print_in_repeat(self): else: print(f'nah') break - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_source_map(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 3 times food = ask 'What do you want?' if food is pizza print 'nice!' else - print 'pizza is better'""") + print 'pizza is better'""" + ) expected_source_map = { - '2/5-2/9': '2/3-2/7', - '2/5-2/35': '2/3-2/37', - '3/8-3/21': '3/6-3/75', - '4/9-4/22': '4/5-4/20', - '3/5-4/31': '7/-197-3/28', - '6/9-6/32': '6/5-6/30', - '4/31-6/41': '7/-197-2/8', - '3/5-6/41': '7/-197-3/62', - '1/1-6/50': '1/1-7/18', - '1/1-6/51': '1/1-7/18' + "2/5-2/9": "2/3-2/7", + "2/5-2/35": "2/3-2/37", + "3/8-3/21": "3/6-3/75", + "4/9-4/22": "4/5-4/20", + "3/5-4/31": "7/-197-3/28", + "6/9-6/32": "6/5-6/30", + "4/31-6/41": "7/-197-2/8", + "3/5-6/41": "7/-197-3/62", + "1/1-6/50": "1/1-7/18", + "1/1-6/51": "1/1-7/18", } self.source_map_tester( diff --git a/tests/test_level/test_level_10.py b/tests/test_level/test_level_10.py index 3e115952292..eb17498dad5 100644 --- a/tests/test_level/test_level_10.py +++ b/tests/test_level/test_level_10.py @@ -6,7 +6,7 @@ class TestsLevel10(HedyTester): level = 10 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -17,123 +17,146 @@ class TestsLevel10(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # for list command # def test_for_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is hond, kat, papegaai for dier in dieren - print dier""") + print dier""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ dieren = ['hond', 'kat', 'papegaai'] for dier in dieren: print(f'{dier}') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( code=code, expected=expected, - expected_commands=['is', 'for', 'print'], - max_level=11 + expected_commands=["is", "for", "print"], + max_level=11, ) def test_blanks(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ players = Ann, John, Jesse choices = 1, 2, 3, 4, 5, 6 _ print player ' throws ' choices at random sleep - """) + """ + ) self.multi_level_tester( max_level=16, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.CodePlaceholdersPresentException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.CodePlaceholdersPresentException, ) def test_for_list_hindi(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ क is hond, kat, papegaai for काउंटर in क - print काउंटर""") + print काउंटर""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ क = ['hond', 'kat', 'papegaai'] for काउंटर in क: print(f'{काउंटर}') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( code=code, expected=expected, - expected_commands=['is', 'for', 'print'], - max_level=11 + expected_commands=["is", "for", "print"], + max_level=11, ) def test_for_list_multiline_body(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ familie is baby, mommy, daddy, grandpa, grandma for shark in familie print shark ' shark tudutudutudu' print shark ' shark tudutudutudu' print shark ' shark tudutudutudu' - print shark ' shark'""") + print shark ' shark'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ familie = ['baby', 'mommy', 'daddy', 'grandpa', 'grandma'] for shark in familie: print(f'{shark} shark tudutudutudu') print(f'{shark} shark tudutudutudu') print(f'{shark} shark tudutudutudu') print(f'{shark} shark') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=11) def test_for_list_with_string_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is 'text' for dier in dieren - print dier""") + print dier""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, max_level=16, - exception=hedy.exceptions.InvalidArgumentTypeException) + exception=hedy.exceptions.InvalidArgumentTypeException, + ) def test_for_list_with_int_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is 5 for dier in dieren - print dier""") + print dier""" + ) self.multi_level_tester( code=code, max_level=16, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) # # if pressed tests # def test_if_pressed_with_list_and_for(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ lijstje is kip, haan, kuiken if x is pressed for dier in lijstje print dier else - print 'onbekend dier'""") + print 'onbekend dier'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ lijstje = ['kip', 'haan', 'kuiken'] pygame_end = False while not pygame_end: @@ -152,9 +175,7 @@ def test_if_pressed_with_list_and_for(self): # End of PyGame Event Handler else: print(f'onbekend dier') - break""") + break""" + ) - self.multi_level_tester( - code=code, - expected=expected, - max_level=11) + self.multi_level_tester(code=code, expected=expected, max_level=11) diff --git a/tests/test_level/test_level_11.py b/tests/test_level/test_level_11.py index 561eb1c93ce..023cc021df4 100644 --- a/tests/test_level/test_level_11.py +++ b/tests/test_level/test_level_11.py @@ -6,7 +6,7 @@ class TestsLevel11(HedyTester): level = 11 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -17,102 +17,124 @@ class TestsLevel11(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # for loop # def test_for_loop(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 1 to 10 - a is i + 1""") - expected = textwrap.dedent("""\ + a is i + 1""" + ) + expected = textwrap.dedent( + """\ step = 1 if int(1) < int(10) else -1 for i in range(int(1), int(10) + step, step): a = int(i) + int(1) - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester( - code=code, - expected=expected, - expected_commands=['for', 'is', 'addition']) + code=code, expected=expected, expected_commands=["for", "is", "addition"] + ) def test_for_loop_with_int_vars(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ begin = 1 end = 10 for i in range begin to end - print i""") + print i""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ begin = '1' end = '10' step = 1 if int(begin) < int(end) else -1 for i in range(int(begin), int(end) + step, step): print(f'{i}') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester(code=code, expected=expected) def test_for_loop_with_list_var_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ begin = 1, 2 end = 3, 4 for i in range begin to end - print i""") + print i""" + ) self.multi_level_tester( code=code, max_level=15, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) def test_for_loop_with_string_var_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ begin = 'one' end = 'ten' for i in range begin to end - print i""") + print i""" + ) self.multi_level_tester( code=code, max_level=16, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) def test_for_loop_multiline_body(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 2 b is 3 for a in range 2 to 4 a is a + 2 - b is b + 2""") + b is b + 2""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = '2' b = '3' step = 1 if int(2) < int(4) else -1 for a in range(int(2), int(4) + step, step): a = int(a) + int(2) b = int(b) + int(2) - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester(code=code, expected=expected) def test_for_loop_followed_by_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 1 to 10 print i - print 'wie niet weg is is gezien'""") + print 'wie niet weg is is gezien'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if int(1) < int(10) else -1 for i in range(int(1), int(10) + step, step): print(f'{i}') time.sleep(0.1) - print(f'wie niet weg is is gezien')""") + print(f'wie niet weg is is gezien')""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ 1 2 3 @@ -123,100 +145,123 @@ def test_for_loop_followed_by_print(self): 8 9 10 - wie niet weg is is gezien""") + wie niet weg is is gezien""" + ) self.single_level_tester( code=code, expected=expected, - expected_commands=['for', 'print', 'print'], - output=output) + expected_commands=["for", "print", "print"], + output=output, + ) def test_for_loop_hindi_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for काउंटर in range 1 to 5 - print काउंटर""") + print काउंटर""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if int(1) < int(5) else -1 for काउंटर in range(int(1), int(5) + step, step): print(f'{काउंटर}') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester( - code=code, - expected=expected, - expected_commands=['for', 'print']) + code=code, expected=expected, expected_commands=["for", "print"] + ) def test_for_loop_arabic_range_latin_output(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for دورة in range ١ to ٥ - print دورة""") + print دورة""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if int(1) < int(5) else -1 for دورة in range(int(1), int(5) + step, step): print(f'{دورة}') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ 1 2 3 4 - 5""") + 5""" + ) self.single_level_tester( code=code, output=output, expected=expected, - expected_commands=['for', 'print']) + expected_commands=["for", "print"], + ) def test_for_loop_arabic_range_arabic_output(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for دورة in range ١ to ٥ - print دورة""") + print دورة""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if int(1) < int(5) else -1 for دورة in range(int(1), int(5) + step, step): print(f'{convert_numerals("Arabic",دورة)}') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ ١ ٢ ٣ ٤ - ٥""") + ٥""" + ) self.single_level_tester( code=code, output=output, - lang='ar', + lang="ar", translate=False, expected=expected, - expected_commands=['for', 'print']) + expected_commands=["for", "print"], + ) def test_for_loop_reversed_range(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 10 to 1 print i - print 'wie niet weg is is gezien'""") + print 'wie niet weg is is gezien'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if int(10) < int(1) else -1 for i in range(int(10), int(1) + step, step): print(f'{i}') time.sleep(0.1) - print(f'wie niet weg is is gezien')""") + print(f'wie niet weg is is gezien')""" + ) self.single_level_tester( - code=code, - expected=expected, - expected_commands=['for', 'print', 'print']) + code=code, expected=expected, expected_commands=["for", "print", "print"] + ) def test_for_loop_with_if_else_body(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 0 to 10 antwoord is ask 'Wat is 5*5' if antwoord is 24 @@ -224,9 +269,11 @@ def test_for_loop_with_if_else_body(self): else print 'Dat is goed!' if antwoord is 25 - i is 10""") + i is 10""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if int(0) < int(10) else -1 for i in range(int(0), int(10) + step, step): antwoord = input(f'Wat is 5*5') @@ -236,71 +283,86 @@ def test_for_loop_with_if_else_body(self): print(f'Dat is goed!') if convert_numerals('Latin', antwoord) == convert_numerals('Latin', '25'): i = '10' - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester(code=code, expected=expected) # issue 363 def test_for_loop_if_followed_by_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 0 to 10 antwoord is ask 'Wat is 5*5' if antwoord is 24 print 'fout' - print 'klaar met for loop'""") + print 'klaar met for loop'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if int(0) < int(10) else -1 for i in range(int(0), int(10) + step, step): antwoord = input(f'Wat is 5*5') if convert_numerals('Latin', antwoord) == convert_numerals('Latin', '24'): print(f'fout') time.sleep(0.1) - print(f'klaar met for loop')""") + print(f'klaar met for loop')""" + ) self.single_level_tester(code=code, expected=expected) # issue 599 def test_for_loop_if(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 0 to 10 if i is 2 - print '2'""") + print '2'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if int(0) < int(10) else -1 for i in range(int(0), int(10) + step, step): if convert_numerals('Latin', i) == convert_numerals('Latin', '2'): print(f'2') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester(code=code, expected=expected) # issue 1209 def test_for_loop_unindented_nested_for_loop(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for x in range 1 to 10 for y in range 1 to 10 - print 'x*y'""") + print 'x*y'""" + ) self.multi_level_tester(code, exception=hedy.exceptions.NoIndentationException) # issue 1209 def test_for_loop_dedented_nested_loop(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for x in range 1 to 10 for y in range 1 to 10 - print 'x*y'""") + print 'x*y'""" + ) self.multi_level_tester(code, exception=hedy.exceptions.NoIndentationException) # issue 1209 def test_nested_for_loop_with_zigzag_body(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for x in range 1 to 10 for y in range 1 to 10 print 'this number is' - print x*y""") + print x*y""" + ) self.multi_level_tester(code, exception=hedy.exceptions.IndentationException) @@ -309,14 +371,17 @@ def test_nested_for_loop_with_zigzag_body(self): # def test_if_pressed_works_in_for_loop(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 1 to 10 if p is pressed print 'press' else - print 'no!'""") + print 'no!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if int(1) < int(10) else -1 for i in range(int(1), int(10) + step, step): pygame_end = False @@ -335,7 +400,8 @@ def test_if_pressed_works_in_for_loop(self): else: print(f'no!') break - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester( code=code, diff --git a/tests/test_level/test_level_12.py b/tests/test_level/test_level_12.py index 84fd1a7c445..2a00c4a2a07 100644 --- a/tests/test_level/test_level_12.py +++ b/tests/test_level/test_level_12.py @@ -9,7 +9,7 @@ class TestsLevel12(HedyTester): level = 12 - ''' + """ Tests should be ordered as follows: * commands in the order of hedy.py e.g. for level 1: ['print', 'ask', 'echo', 'turn', 'forward'] * combined tests @@ -20,35 +20,32 @@ class TestsLevel12(HedyTester): * single keyword positive tests are just keyword or keyword_special_case * multi keyword positive tests are keyword1_keywords_2 * negative tests should be situation_gives_exception - ''' + """ # # print tests # def test_print_float_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ pi is 3.14 - print pi""") - expected = textwrap.dedent("""\ + print pi""" + ) + expected = textwrap.dedent( + """\ pi = 3.14 - print(f'''{pi}''')""") - - self.multi_level_tester( - code=code, - max_level=17, - expected=expected + print(f'''{pi}''')""" ) + self.multi_level_tester(code=code, max_level=17, expected=expected) + def test_print_float(self): code = "print 3.14" expected = "print(f'''3.14''')" self.multi_level_tester( - code=code, - max_level=17, - expected=expected, - output='3.14' + code=code, max_level=17, expected=expected, output="3.14" ) def test_print_division_float(self): @@ -57,10 +54,7 @@ def test_print_division_float(self): output = "1.5" self.multi_level_tester( - code=code, - expected=expected, - max_level=17, - output=output + code=code, expected=expected, max_level=17, output=output ) def test_print_literal_strings(self): @@ -82,155 +76,141 @@ def test_print_line_with_spaces_works(self): code=code, expected=expected, expected_commands=expected_commands, - max_level=17) + max_level=17, + ) def test_print_string_with_triple_quotes_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ var = " is not allowed" - print "'''" + var """) + print "'''" + var """ + ) self.multi_level_tester( - code=code, - max_level=17, - exception=hedy.exceptions.UnsupportedStringValue + code=code, max_level=17, exception=hedy.exceptions.UnsupportedStringValue ) # issue #745 def test_print_list_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ plaatsen is 1, 2, 3 - print plaatsen""") + print plaatsen""" + ) self.multi_level_tester( code=code, max_level=15, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_print_subtraction_with_text(self): code = "print 'And the winner is ' 5 - 5" expected = "print(f'''And the winner is {5 - 5}''')" - output = 'And the winner is 0' + output = "And the winner is 0" - self.multi_level_tester(max_level=17, code=code, expected=expected, output=output) + self.multi_level_tester( + max_level=17, code=code, expected=expected, output=output + ) def test_print_list_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ numbers is 1, 2, 4 - print numbers at random""") + print numbers at random""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ numbers = [1, 2, 4]""", - HedyTester.list_access_transpiled('random.choice(numbers)'), - "print(f'''{random.choice(numbers)}''')") + HedyTester.list_access_transpiled("random.choice(numbers)"), + "print(f'''{random.choice(numbers)}''')", + ) self.multi_level_tester( code=code, max_level=15, expected=expected, - expected_commands=['is', 'print', 'random'] + expected_commands=["is", "print", "random"], ) def test_print_list_access_index(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ numbers is 5, 4, 3 - print numbers at 1""") + print numbers at 1""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ numbers = [5, 4, 3]""", - HedyTester.list_access_transpiled('numbers[int(1)-1]'), - "print(f'''{numbers[int(1)-1]}''')") + HedyTester.list_access_transpiled("numbers[int(1)-1]"), + "print(f'''{numbers[int(1)-1]}''')", + ) - check_in_list = (lambda x: HedyTester.run_code(x) == '5') + def check_in_list(x): + return HedyTester.run_code(x) == "5" self.multi_level_tester( max_level=15, code=code, expected=expected, - extra_check_function=check_in_list + extra_check_function=check_in_list, ) def test_print_single_quoted_text(self): code = "print 'hallo wereld!'" expected = "print(f'''hallo wereld!''')" - self.multi_level_tester( - code=code, - max_level=17, - expected=expected) + self.multi_level_tester(code=code, max_level=17, expected=expected) def test_print_chinese_quoted_text(self): code = "print “逃离鬼屋!”" expected = "print(f'''逃离鬼屋!''')" - self.multi_level_tester( - code=code, - max_level=17, - expected=expected) + self.multi_level_tester(code=code, max_level=17, expected=expected) def test_print_french_quoted_text(self): code = "print «bonjour tous le monde!»" expected = "print(f'''bonjour tous le monde!''')" - self.multi_level_tester( - code=code, - max_level=17, - expected=expected) + self.multi_level_tester(code=code, max_level=17, expected=expected) def test_print_chinese_double_quoted_text(self): code = "print ‘逃离鬼屋!’" expected = "print(f'''逃离鬼屋!''')" - self.multi_level_tester( - code=code, - max_level=17, - expected=expected) + self.multi_level_tester(code=code, max_level=17, expected=expected) def test_print_double_quoted_text(self): code = 'print "hallo wereld!"' expected = "print(f'''hallo wereld!''')" - self.multi_level_tester( - code=code, - max_level=17, - expected=expected) + self.multi_level_tester(code=code, max_level=17, expected=expected) def test_print_single_quoted_text_with_inner_double_quote(self): code = """print 'quote is "'""" expected = """print(f'''quote is "''')""" - self.multi_level_tester( - code=code, - max_level=17, - expected=expected) + self.multi_level_tester(code=code, max_level=17, expected=expected) def test_print_double_quoted_text_with_inner_single_quote(self): code = '''print "It's me"''' expected = """print(f'''It\\'s me''')""" - self.multi_level_tester( - code=code, - max_level=17, - expected=expected) + self.multi_level_tester(code=code, max_level=17, expected=expected) def test_print_no_space(self): code = "print'hallo wereld!'" expected = "print(f'''hallo wereld!''')" - self.multi_level_tester( - code=code, - max_level=17, - expected=expected) + self.multi_level_tester(code=code, max_level=17, expected=expected) def test_print_comma(self): code = "print 'Hi, I am Hedy'" expected = "print(f'''Hi, I am Hedy''')" - self.multi_level_tester( - code=code, - max_level=17, - expected=expected - ) + self.multi_level_tester(code=code, max_level=17, expected=expected) def test_print_slash(self): code = "print 'Yes/No'" @@ -244,21 +224,14 @@ def test_print_backslash(self): output = "Yes\\No" self.multi_level_tester( - code=code, - expected=expected, - output=output, - max_level=17, - translate=True + code=code, expected=expected, output=output, max_level=17, translate=True ) def test_print_with_backslash_at_end(self): code = "print 'Welcome to \\'" expected = "print(f'''Welcome to \\\\''')" self.multi_level_tester( - code=code, - max_level=17, - expected=expected, - translate=True + code=code, max_level=17, expected=expected, translate=True ) def test_print_with_spaces(self): @@ -274,38 +247,50 @@ def test_print_asterisk(self): self.multi_level_tester(code=code, expected=expected, max_level=17) def test_print_single_quoted_text_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is "'Hedy'" - print 'ik heet ' naam""") + print 'ik heet ' naam""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = "'Hedy'" - print(f'''ik heet {naam}''')""") + print(f'''ik heet {naam}''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) def test_print_double_quoted_text_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is '"Hedy"' - print 'ik heet ' naam""") + print 'ik heet ' naam""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = '"Hedy"' - print(f'''ik heet {naam}''')""") + print(f'''ik heet {naam}''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) # issue 1795 def test_print_quoted_var_reference(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is "'Daan'" woord1 is 'zomerkamp' - print 'naam' ' is naar het' 'woord1'""") + print 'naam' ' is naar het' 'woord1'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = "'Daan'" woord1 = 'zomerkamp' - print(f'''naam is naar hetwoord1''')""") + print(f'''naam is naar hetwoord1''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) @@ -324,74 +309,94 @@ def test_print_concat_double_quoted_strings_with_inner_single_quotes(self): @parameterized.expand(HedyTester.quotes) def test_print_concat_var_and_literal_string(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ hi = {q}Hi{q} - print hi + {q} there{q}""") - expected = textwrap.dedent("""\ + print hi + {q} there{q}""" + ) + expected = textwrap.dedent( + """\ hi = 'Hi' - print(f'''{hi + ' there'}''')""") + print(f'''{hi + ' there'}''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) def test_print_chained_assignments(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ x is 1 + 2 y is x + 3 - print y + 4""") + print y + 4""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ x = 1 + 2 y = x + 3 - print(f'''{y + 4}''')""") + print(f'''{y + 4}''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) def test_assign_to_list_access(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ field = '.', '.', '.', '.', '.', '.' field at 1 = 'x' - print field at 1""") + print field at 1""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ field = ['.', '.', '.', '.', '.', '.'] field[int(1)-1] = 'x' try: field[int(1)-1] except IndexError: raise Exception('catch_index_exception') - print(f'''{field[int(1)-1]}''')""") + print(f'''{field[int(1)-1]}''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=15) def test_if_and_list_access(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ player = 'x' choice = 1 field = '.', '.', '.', '.', '.', '.', '.', '.', '.' if field at choice = '.' field at choice = player else - print 'illegal move!'""") + print 'illegal move!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ player = 'x' choice = 1 field = ['.', '.', '.', '.', '.', '.', '.', '.', '.'] if convert_numerals('Latin', field[int(choice)-1]) == convert_numerals('Latin', '.'): field[int(choice)-1] = player else: - print(f'''illegal move!''')""") + print(f'''illegal move!''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=15) def test_print_calc(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ var is 5 - print var + 5""") + print var + 5""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ var = 5 - print(f'''{var + 5}''')""") + print(f'''{var + 5}''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) @@ -399,12 +404,14 @@ def test_print_calc(self): # forward tests # def test_forward_with_integer_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 50 - forward a""") + forward a""" + ) expected = HedyTester.dedent( - "a = 50", - HedyTester.forward_transpiled('a', self.level)) + "a = 50", HedyTester.forward_transpiled("a", self.level) + ) self.multi_level_tester( code=code, @@ -413,25 +420,31 @@ def test_forward_with_integer_variable(self): ) def test_forward_with_string_variable_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is "ten" - forward a""") + forward a""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_forward_with_list_access_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ directions is 10, 100, 360 - forward directions at random""") + forward directions at random""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ directions = [10, 100, 360]""", - HedyTester.list_access_transpiled('random.choice(directions)'), - HedyTester.forward_transpiled('random.choice(directions)', self.level)) + HedyTester.list_access_transpiled("random.choice(directions)"), + HedyTester.forward_transpiled("random.choice(directions)", self.level), + ) self.multi_level_tester( max_level=15, @@ -444,88 +457,99 @@ def test_forward_with_list_access_random(self): # turn # def test_turn_with_number_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ direction is 70 - turn direction""") + turn direction""" + ) expected = HedyTester.dedent( - "direction = 70", - HedyTester.turn_transpiled('direction', self.level)) + "direction = 70", HedyTester.turn_transpiled("direction", self.level) + ) self.multi_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_turtle() + code=code, expected=expected, extra_check_function=self.is_turtle() ) def test_turn_with_non_latin_float_number_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ الزاوية هو ٩.٠ استدر الزاوية - تقدم ١٠.١٠""") + تقدم ١٠.١٠""" + ) expected = HedyTester.dedent( "الزاوية = 9.0", HedyTester.turn_transpiled("الزاوية", self.level), - HedyTester.forward_transpiled("10.1", self.level) + HedyTester.forward_transpiled("10.1", self.level), ) self.multi_level_tester( code=code, - lang='ar', + lang="ar", expected=expected, - extra_check_function=self.is_turtle() + extra_check_function=self.is_turtle(), ) def test_turtle_with_expression(self): - - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ num = 10.6 turn num + 10.5 - forward 10.5 + num""") + forward 10.5 + num""" + ) expected = HedyTester.dedent( "num = 10.6", - HedyTester.turn_transpiled('num + 10.5', self.level), - HedyTester.forward_transpiled('10.5 + num', self.level) + HedyTester.turn_transpiled("num + 10.5", self.level), + HedyTester.forward_transpiled("10.5 + num", self.level), ) self.multi_level_tester(code=code, expected=expected) def test_turn_with_string_var_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ direction is 'ten' - turn direction""") + turn direction""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_turn_with_non_ascii_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ ángulo is 90 - turn ángulo""") + turn ángulo""" + ) expected = HedyTester.dedent( - "ángulo = 90", - HedyTester.turn_transpiled('ángulo', self.level)) + "ángulo = 90", HedyTester.turn_transpiled("ángulo", self.level) + ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_turtle(), - expected_commands=['is', 'turn'] + expected_commands=["is", "turn"], ) def test_turn_with_list_access_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ directions is 10, 100, 360 - turn directions at random""") + turn directions at random""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ directions = [10, 100, 360]""", - HedyTester.list_access_transpiled('random.choice(directions)'), - HedyTester.turn_transpiled('random.choice(directions)', self.level)) + HedyTester.list_access_transpiled("random.choice(directions)"), + HedyTester.turn_transpiled("random.choice(directions)", self.level), + ) self.multi_level_tester( max_level=15, @@ -535,11 +559,14 @@ def test_turn_with_list_access_random(self): ) def test_ask_forward(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ afstand is ask 'hoe ver dan?' - forward afstand""") + forward afstand""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ afstand = input(f'''hoe ver dan?''') try: afstand = int(afstand) @@ -548,13 +575,14 @@ def test_ask_forward(self): afstand = float(afstand) except ValueError: pass""", - HedyTester.forward_transpiled('afstand', self.level)) + HedyTester.forward_transpiled("afstand", self.level), + ) self.multi_level_tester( max_level=17, code=code, expected=expected, - extra_check_function=self.is_turtle() + extra_check_function=self.is_turtle(), ) # @@ -563,33 +591,29 @@ def test_ask_forward(self): def test_print_comment(self): code = "print 'Hallo welkom bij Hedy!' # This is a comment" expected = "print(f'''Hallo welkom bij Hedy!''')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" self.multi_level_tester( - max_level=17, - code=code, - expected=expected, - output=output + max_level=17, code=code, expected=expected, output=output ) def test_assign_comment(self): code = 'test = "Welkom bij Hedy" # This is a comment' expected = "test = 'Welkom bij Hedy'" - self.multi_level_tester( - max_level=18, - code=code, - expected=expected - ) + self.multi_level_tester(max_level=18, code=code, expected=expected) # # ask tests # def test_ask_number_answer(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ prijs is ask 'hoeveel?' gespaard is 7 - sparen is prijs - gespaard""") - expected = textwrap.dedent("""\ + sparen is prijs - gespaard""" + ) + expected = textwrap.dedent( + """\ prijs = input(f'''hoeveel?''') try: prijs = int(prijs) @@ -599,16 +623,20 @@ def test_ask_number_answer(self): except ValueError: pass gespaard = 7 - sparen = prijs - gespaard""") + sparen = prijs - gespaard""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) def test_ask_with_list_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is 'orange', 'blue', 'green' - favorite is ask 'Is your fav color' colors at 1""") + favorite is ask 'Is your fav color' colors at 1""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ colors = ['orange', 'blue', 'green'] favorite = input(f'''Is your fav color{colors[int(1)-1]}''') try: @@ -617,13 +645,15 @@ def test_ask_with_list_var(self): try: favorite = float(favorite) except ValueError: - pass""") + pass""" + ) self.multi_level_tester(code=code, expected=expected, max_level=14) def test_ask_literal_strings(self): code = """var is ask "It's " '"Hedy"!'""" - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ var = input(f'''It\\'s "Hedy"!''') try: var = int(var) @@ -631,17 +661,21 @@ def test_ask_literal_strings(self): try: var = float(var) except ValueError: - pass""") + pass""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) @parameterized.expand(HedyTester.quotes) def test_ask_with_string_var(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ color is {q}orange{q} - favorite is ask {q}Is your fav color{q} color""") + favorite is ask {q}Is your fav color{q} color""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ color = 'orange' favorite = input(f'''Is your fav color{color}''') try: @@ -650,17 +684,21 @@ def test_ask_with_string_var(self, q): try: favorite = float(favorite) except ValueError: - pass""") + pass""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) - @parameterized.expand(['10', '10.0']) + @parameterized.expand(["10", "10.0"]) def test_ask_with_number_var(self, number): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ number is {number} - favorite is ask 'Is your fav number' number""") + favorite is ask 'Is your fav number' number""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ number = {number} favorite = input(f'''Is your fav number{{number}}''') try: @@ -669,103 +707,122 @@ def test_ask_with_number_var(self, number): try: favorite = float(favorite) except ValueError: - pass""") + pass""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) def test_ask_list_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ numbers is 1, 2, 3 - favorite is ask 'Is your fav number' numbers""") + favorite is ask 'Is your fav number' numbers""" + ) self.multi_level_tester( max_level=15, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_ask_single_quoted_text(self): code = "details is ask 'tell me more'" - expected = HedyTester.input_transpiled('details', 'tell me more') + expected = HedyTester.input_transpiled("details", "tell me more") self.multi_level_tester(code=code, expected=expected, max_level=17) def test_ask_double_quoted_text(self): code = 'details is ask "tell me more"' - expected = HedyTester.input_transpiled('details', 'tell me more') + expected = HedyTester.input_transpiled("details", "tell me more") self.multi_level_tester(code=code, expected=expected, max_level=17) def test_ask_single_quoted_text_with_inner_double_quote(self): code = """details is ask 'say "no"'""" - expected = HedyTester.input_transpiled('details', 'say "no"') + expected = HedyTester.input_transpiled("details", 'say "no"') self.multi_level_tester(code=code, expected=expected, max_level=17) def test_ask_double_quoted_text_with_inner_single_quote(self): code = f'''details is ask "say 'no'"''' - expected = HedyTester.input_transpiled('details', "say \\'no\\'") + expected = HedyTester.input_transpiled("details", "say \\'no\\'") self.multi_level_tester(code=code, expected=expected, max_level=17) def test_ask_with_comma(self): code = "dieren is ask 'hond, kat, kangoeroe'" - expected = HedyTester.input_transpiled('dieren', 'hond, kat, kangoeroe') + expected = HedyTester.input_transpiled("dieren", "hond, kat, kangoeroe") self.multi_level_tester(code=code, expected=expected, max_level=17) @parameterized.expand(HedyTester.quotes) def test_ask_es(self, q): code = f"""color is ask {q}Cuál es tu color favorito?{q}""" - expected = HedyTester.input_transpiled('color', 'Cuál es tu color favorito?') + expected = HedyTester.input_transpiled("color", "Cuál es tu color favorito?") self.multi_level_tester(code=code, expected=expected, max_level=17) @parameterized.expand(HedyTester.quotes) def test_ask_bengali_var(self, q): code = f"""রং is ask {q}আপনার প্রিয় রং কি?{q}""" - expected = HedyTester.input_transpiled('রং', 'আপনার প্রিয় রং কি?') + expected = HedyTester.input_transpiled("রং", "আপনার প্রিয় রং কি?") self.multi_level_tester(code=code, expected=expected, max_level=17) def test_ask_list_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ numbers is 1, 2, 3 - favorite is ask 'Is your fav number ' numbers at random""") + favorite is ask 'Is your fav number ' numbers at random""" + ) expected = HedyTester.dedent( "numbers = [1, 2, 3]", - HedyTester.input_transpiled('favorite', 'Is your fav number {random.choice(numbers)}')) + HedyTester.input_transpiled( + "favorite", "Is your fav number {random.choice(numbers)}" + ), + ) self.multi_level_tester(code=code, expected=expected, max_level=15) def test_ask_list_access_index(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ numbers is 1, 2, 3 - favorite is ask 'Is your fav number ' numbers at 2""") + favorite is ask 'Is your fav number ' numbers at 2""" + ) expected = HedyTester.dedent( "numbers = [1, 2, 3]", - HedyTester.input_transpiled('favorite', 'Is your fav number {numbers[int(2)-1]}')) + HedyTester.input_transpiled( + "favorite", "Is your fav number {numbers[int(2)-1]}" + ), + ) self.multi_level_tester(code=code, expected=expected, max_level=15) def test_ask_string_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is "orange" - favorite is ask 'Is your fav color ' color""") + favorite is ask 'Is your fav color ' color""" + ) expected = HedyTester.dedent( "color = 'orange'", - HedyTester.input_transpiled('favorite', 'Is your fav color {color}')) + HedyTester.input_transpiled("favorite", "Is your fav color {color}"), + ) self.multi_level_tester(code=code, expected=expected, max_level=17) def test_ask_integer_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ number is 10 - favorite is ask 'Is your fav number ' number""") + favorite is ask 'Is your fav number ' number""" + ) expected = HedyTester.dedent( "number = 10", - HedyTester.input_transpiled('favorite', 'Is your fav number {number}')) + HedyTester.input_transpiled("favorite", "Is your fav number {number}"), + ) self.multi_level_tester(code=code, expected=expected, max_level=17) @@ -773,30 +830,36 @@ def test_ask_integer_var(self): # sleep tests # def test_sleep_with_number_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 2 - sleep n""") - expected = HedyTester.dedent( - "n = 2", - HedyTester.sleep_command_transpiled("n")) + sleep n""" + ) + expected = HedyTester.dedent("n = 2", HedyTester.sleep_command_transpiled("n")) self.multi_level_tester(code=code, expected=expected) def test_sleep_with_string_variable_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is "test" - sleep n""") + sleep n""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) def test_sleep_with_list_access(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1, 2, 3 - sleep n at 1""") - expected = textwrap.dedent("""\ + sleep n at 1""" + ) + expected = textwrap.dedent( + """\ n = [1, 2, 3] try: try: @@ -805,16 +868,20 @@ def test_sleep_with_list_access(self): raise Exception('catch_index_exception') time.sleep(int(n[int(1)-1])) except ValueError: - raise Exception(f'While running your program the command sleep received the value {n[int(1)-1]} which is not allowed. Try changing the value to a number.')""") + raise Exception(f'While running your program the command sleep received the value {n[int(1)-1]} which is not allowed. Try changing the value to a number.')""" + ) self.multi_level_tester(max_level=15, code=code, expected=expected) def test_sleep_with_list_random(self): self.maxDiff = None - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1, 2, 3 - sleep n at random""") - expected = textwrap.dedent("""\ + sleep n at random""" + ) + expected = textwrap.dedent( + """\ n = [1, 2, 3] try: try: @@ -823,26 +890,33 @@ def test_sleep_with_list_random(self): raise Exception('catch_index_exception') time.sleep(int(random.choice(n))) except ValueError: - raise Exception(f'While running your program the command sleep received the value {random.choice(n)} which is not allowed. Try changing the value to a number.')""") + raise Exception(f'While running your program the command sleep received the value {random.choice(n)} which is not allowed. Try changing the value to a number.')""" + ) self.multi_level_tester(max_level=15, code=code, expected=expected) def test_sleep_with_list_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1, 2, 3 - sleep n""") + sleep n""" + ) self.multi_level_tester( max_level=15, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) def test_sleep_with_input_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is ask "how long" - sleep n""") - expected = HedyTester.dedent("""\ + sleep n""" + ) + expected = HedyTester.dedent( + """\ n = input(f'''how long''') try: n = int(n) @@ -851,29 +925,35 @@ def test_sleep_with_input_variable(self): n = float(n) except ValueError: pass""", - HedyTester.sleep_command_transpiled("n")) + HedyTester.sleep_command_transpiled("n"), + ) self.multi_level_tester(max_level=17, code=code, expected=expected) def test_sleep_with_calc(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1 * 2 + 3 - sleep n""") + sleep n""" + ) expected = HedyTester.dedent( - "n = 1 * 2 + 3", - HedyTester.sleep_command_transpiled("n")) + "n = 1 * 2 + 3", HedyTester.sleep_command_transpiled("n") + ) self.multi_level_tester(code=code, expected=expected) def test_sleep_with_float_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 1.5 - sleep n""") + sleep n""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) # # assign tests @@ -891,18 +971,19 @@ def test_assign_list(self): self.multi_level_tester(code=code, expected=expected, max_level=15) def test_assign_list_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is 'hond', 'kat', 'kangoeroe' - dier is dieren at random""") + dier is dieren at random""" + ) - expected = HedyTester.dedent("dieren = ['hond', 'kat', 'kangoeroe']", - HedyTester.list_access_transpiled('random.choice(dieren)'), - "dier = random.choice(dieren)") + expected = HedyTester.dedent( + "dieren = ['hond', 'kat', 'kangoeroe']", + HedyTester.list_access_transpiled("random.choice(dieren)"), + "dier = random.choice(dieren)", + ) - self.multi_level_tester( - code=code, - expected=expected, - max_level=15) + self.multi_level_tester(code=code, expected=expected, max_level=15) def test_assign_list_with_dutch_comma_arabic_lang(self): code = "صديقي هو 'احمد', 'خالد', 'حسن'" @@ -911,33 +992,23 @@ def test_assign_list_with_dutch_comma_arabic_lang(self): self.multi_level_tester( code=code, expected=expected, - lang='ar', + lang="ar", max_level=15, # translation must be off because the Latin commas will be converted to arabic commas and this is correct - translate=False + translate=False, ) def test_assign_list_with_arabic_comma_and_is(self): code = "animals هو 'cat'، 'dog'، 'platypus'" expected = "animals = ['cat', 'dog', 'platypus']" - self.multi_level_tester( - max_level=15, - code=code, - expected=expected, - lang='ar' - ) + self.multi_level_tester(max_level=15, code=code, expected=expected, lang="ar") def test_assign_list_with_arabic_comma(self): code = "صديقي هو 'احمد'، 'خالد'، 'حسن'" expected = "صديقي = ['احمد', 'خالد', 'حسن']" - self.multi_level_tester( - max_level=15, - code=code, - expected=expected, - lang='ar' - ) + self.multi_level_tester(max_level=15, code=code, expected=expected, lang="ar") def test_assign_string_without_quotes(self): code = "name is felienne" @@ -946,19 +1017,21 @@ def test_assign_string_without_quotes(self): code=code, max_level=17, exception=hedy.exceptions.UnquotedAssignTextException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1 + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, ) def test_assign_string_without_quotes_line_2(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print 'lalala' - name is Harry""") + name is Harry""" + ) self.multi_level_tester( code=code, max_level=17, exception=hedy.exceptions.UnquotedAssignTextException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2 + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, ) @parameterized.expand(HedyTester.quotes) @@ -1009,91 +1082,109 @@ def test_assign_concat(self): # add/remove tests # def test_add_ask_to_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is ask 'what is your favorite color?' colors is 'green', 'red', 'blue' - add color to colors""") + add color to colors""" + ) expected = HedyTester.dedent( - HedyTester.input_transpiled('color', 'what is your favorite color?'), + HedyTester.input_transpiled("color", "what is your favorite color?"), "colors = ['green', 'red', 'blue']", - "colors.append(color)") + "colors.append(color)", + ) self.multi_level_tester(code=code, expected=expected, max_level=15) def test_remove_ask_from_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is 'green', 'red', 'blue' color is ask 'what color to remove?' - remove color from colors""") + remove color from colors""" + ) expected = HedyTester.dedent( "colors = ['green', 'red', 'blue']", - HedyTester.input_transpiled('color', 'what color to remove?'), - HedyTester.remove_transpiled('colors', 'color')) + HedyTester.input_transpiled("color", "what color to remove?"), + HedyTester.remove_transpiled("colors", "color"), + ) self.multi_level_tester(code=code, expected=expected, max_level=15) def test_add_to_list_with_string_var_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is 'yellow' colors is 'green', 'red', 'blue' - add colors to color""") + add colors to color""" + ) self.multi_level_tester( max_level=15, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_add_to_list_with_input_var_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is ask 'What are the colors?' favorite is 'red' - add favorite to colors""") + add favorite to colors""" + ) self.multi_level_tester( max_level=15, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_remove_from_list_with_string_var_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is 'yellow' colors is 'green', 'red', 'blue' - remove colors from color""") + remove colors from color""" + ) self.multi_level_tester( max_level=15, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_remove_from_list_with_input_var_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors is ask 'What are the colors?' favorite is 'red' - remove favorite from colors""") + remove favorite from colors""" + ) self.multi_level_tester( max_level=15, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_list_creation_with_numbers(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ getallen is 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 - getal is getallen at random""") - expected = HedyTester.dedent("""\ + getal is getallen at random""" + ) + expected = HedyTester.dedent( + """\ getallen = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]""", - HedyTester.list_access_transpiled('random.choice(getallen)'), - "getal = random.choice(getallen)") + HedyTester.list_access_transpiled("random.choice(getallen)"), + "getal = random.choice(getallen)", + ) self.multi_level_tester(code=code, expected=expected, max_level=15) @@ -1101,21 +1192,26 @@ def test_list_creation_with_numbers(self): # for loop tests # def test_for_loop_arabic(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for دورة in range ١ to ٥ - print دورة""") + print دورة""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if 1 < 5 else -1 for دورة in range(1, 5 + step, step): print(f'''{دورة}''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( max_level=16, code=code, expected=expected, - expected_commands=['for', 'print']) + expected_commands=["for", "print"], + ) def test_assign_list_with_spaces(self): code = "voorspellingen = 'je wordt rijk' , 'je wordt verliefd' , 'je glijdt uit over een bananenschil'" @@ -1128,105 +1224,139 @@ def test_assign_list_with_spaces(self): # @parameterized.expand(HedyTester.equality_comparison_with_is) def test_if_equality_print(self, eq): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam = 'Hedy' if naam {eq} 'Hedy' - print 'koekoek'""") + print 'koekoek'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'''koekoek''')""") + print(f'''koekoek''')""" + ) self.multi_level_tester( code=code, expected=expected, - expected_commands=['is', 'if', 'print'], - max_level=16) + expected_commands=["is", "if", "print"], + max_level=16, + ) def test_if_equality_no_spaces_print(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam = 'Hedy' if naam='Hedy' - print 'koekoek'""") + print 'koekoek'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'''koekoek''')""") + print(f'''koekoek''')""" + ) self.multi_level_tester( code=code, expected=expected, - expected_commands=['is', 'if', 'print'], - max_level=16) # space between = is not preserved (but is needed for the test) + expected_commands=["is", "if", "print"], + max_level=16, + ) # space between = is not preserved (but is needed for the test) def test_if_equality_rhs_with_space(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 'James' if naam is 'James Bond' - print 'shaken'""") + print 'shaken'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'James' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'James Bond'): - print(f'''shaken''')""") + print(f'''shaken''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_if_equality_single_quoted_rhs_with_inner_double_quote(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ answer is 'no' if answer is 'He said "no"' - print 'no'""") + print 'no'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ answer = 'no' if convert_numerals('Latin', answer) == convert_numerals('Latin', 'He said "no"'): - print(f'''no''')""") + print(f'''no''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_if_equality_double_quoted_rhs_with_inner_single_quote(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ answer is 'no' if answer is "He said 'no'" - print 'no'""") + print 'no'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ answer = 'no' if convert_numerals('Latin', answer) == convert_numerals('Latin', 'He said \\'no\\''): - print(f'''no''')""") + print(f'''no''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_if_equality_negative_number(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord = -10 if antwoord is -10 - print 'Nice'""") + print 'Nice'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ antwoord = -10 if convert_numerals('Latin', antwoord) == convert_numerals('Latin', '-10'): - print(f'''Nice''')""") + print(f'''Nice''')""" + ) - self.multi_level_tester(code=code, expected=expected, output='Nice', max_level=16) + self.multi_level_tester( + code=code, expected=expected, output="Nice", max_level=16 + ) def test_if_2_vars_equality_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ jouwkeuze is 'schaar' computerkeuze is 'schaar' if computerkeuze is jouwkeuze - print 'gelijkspel!'""") + print 'gelijkspel!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ jouwkeuze = 'schaar' computerkeuze = 'schaar' if convert_numerals('Latin', computerkeuze) == convert_numerals('Latin', jouwkeuze): - print(f'''gelijkspel!''')""") + print(f'''gelijkspel!''')""" + ) - self.multi_level_tester(max_level=16, code=code, expected=expected, output='gelijkspel!') + self.multi_level_tester( + max_level=16, code=code, expected=expected, output="gelijkspel!" + ) # def test_if_equality_trailing_space_linebreak_print(self): # code = textwrap.dedent("""\ @@ -1242,162 +1372,195 @@ def test_if_2_vars_equality_print(self): # self.multi_level_tester(max_level=18, code=code, expected=expected) def test_if_equality_lists(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ m is 1, 2 n is 1, 2 if m is n - print 'success!'""") + print 'success!'""" + ) # FH, Mar 2023 why should this fail? self.multi_level_tester( max_level=13, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) @parameterized.expand(HedyTester.quotes) def test_if_in_list_with_string_var_gives_type_error(self, q): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ items is {q}red{q} if {q}red{q} in items - print {q}found!{q}""") + print {q}found!{q}""" + ) self.multi_level_tester( max_level=16, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_if_equality_with_list_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is 5, 6, 7 if 1 is color - print 'success!'""") + print 'success!'""" + ) self.multi_level_tester( max_level=13, code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_if_equality_with_incompatible_types_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 'test' b is 15 if a is b - c is 1""") + c is 1""" + ) self.multi_level_tester( max_level=16, code=code, exception=hedy.exceptions.InvalidTypeCombinationException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3 + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, ) # # if else tests # def test_if_equality_print_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 'Hedy' if naam is 'Hedy' print 'leuk' else - print 'minder leuk'""") + print 'minder leuk'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): print(f'''leuk''') else: - print(f'''minder leuk''')""") + print(f'''minder leuk''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_if_equality_assign_else_assign(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 5 if a is 1 x is 2 else - x is 222""") - expected = textwrap.dedent("""\ + x is 222""" + ) + expected = textwrap.dedent( + """\ a = 5 if convert_numerals('Latin', a) == convert_numerals('Latin', '1'): x = 2 else: - x = 222""") + x = 222""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_if_else_followed_by_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ kleur is 'geel' if kleur is 'groen' antwoord is 'ok' else antwoord is 'stom' - print antwoord""") + print antwoord""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ kleur = 'geel' if convert_numerals('Latin', kleur) == convert_numerals('Latin', 'groen'): antwoord = 'ok' else: antwoord = 'stom' - print(f'''{antwoord}''')""") + print(f'''{antwoord}''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_if_else_trailing_space_after_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 1 if a is 1 print a else - print 'nee'""") + print 'nee'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = 1 if convert_numerals('Latin', a) == convert_numerals('Latin', '1'): print(f'''{a}''') else: - print(f'''nee''')""") + print(f'''nee''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_if_empty_line_with_whitespace_else_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if 1 is 2 sleep else - sleep""") + sleep""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ if convert_numerals('Latin', '1') == convert_numerals('Latin', '2'): time.sleep(1) else: - time.sleep(1)""") + time.sleep(1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_if_else_with_multiple_lines(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord is ask 'Hoeveel is 10 plus 10?' if antwoord is 20 print 'Goedzo!' print 'Het antwoord was inderdaad ' antwoord else print 'Foutje' - print 'Het antwoord moest zijn ' antwoord""") + print 'Het antwoord moest zijn ' antwoord""" + ) expected = HedyTester.dedent( - HedyTester.input_transpiled('antwoord', 'Hoeveel is 10 plus 10?'), """\ + HedyTester.input_transpiled("antwoord", "Hoeveel is 10 plus 10?"), + """\ if convert_numerals('Latin', antwoord) == convert_numerals('Latin', '20'): print(f'''Goedzo!''') print(f'''Het antwoord was inderdaad {antwoord}''') else: print(f'''Foutje''') - print(f'''Het antwoord moest zijn {antwoord}''')""") + print(f'''Het antwoord moest zijn {antwoord}''')""", + ) self.multi_level_tester(code=code, expected=expected, max_level=16) @@ -1405,129 +1568,189 @@ def test_if_else_with_multiple_lines(self): # repeat tests # def test_repeat_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 5 times - print 'koekoek'""") + print 'koekoek'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for i in range(int('5')): print(f'''koekoek''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) def test_repeat_print_variable(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n is 5 repeat n times - print 'me wants a cookie!'""") + print 'me wants a cookie!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ n = 5 for i in range(int(n)): print(f'''me wants a cookie!''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) - self.multi_level_tester(code=code, expected=expected, output=output, max_level=17) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=17 + ) # issue 297 def test_repeat_print_assign_addition(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ count is 1 repeat 12 times print count ' times 12 is ' count * 12 - count is count + 1""") + count is count + 1""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ count = 1 for i in range(int('12')): print(f'''{count} times 12 is {count * 12}''') count = count + 1 - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) def test_repeat_with_comment(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 5 times #This should be ignored - sleep""") + sleep""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for i in range(int('5')): time.sleep(1) - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) - @parameterized.expand(['5', '𑁫', '५', '૫', '੫', '৫', '೫', '୫', '൫', '௫', - '౫', '၅', '༥', '᠕', '៥', '๕', '໕', '꧕', '٥', '۵']) + @parameterized.expand( + [ + "5", + "𑁫", + "५", + "૫", + "੫", + "৫", + "೫", + "୫", + "൫", + "௫", + "౫", + "၅", + "༥", + "᠕", + "៥", + "๕", + "໕", + "꧕", + "٥", + "۵", + ] + ) def test_repeat_with_all_numerals(self, number): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ repeat {number} times - print 'me wants a cookie!'""") + print 'me wants a cookie!'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ for i in range(int('{int(number)}')): print(f'''me wants a cookie!''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - me wants a cookie!""") + me wants a cookie!""" + ) - self.multi_level_tester(code=code, expected=expected, output=output, max_level=17) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=17 + ) def test_repeat_with_variable_name_collision(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ i is 'hallo!' repeat 5 times print 'me wants a cookie!' - print i""") + print i""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ i = 'hallo!' for _i in range(int('5')): print(f'''me wants a cookie!''') time.sleep(0.1) - print(f'''{i}''')""") + print(f'''{i}''')""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! me wants a cookie! - hallo!""") + hallo!""" + ) self.multi_level_tester( code=code, expected=expected, - expected_commands=['is', 'repeat', 'print', 'print'], + expected_commands=["is", "repeat", "print", "print"], output=output, - max_level=17 + max_level=17, ) def test_repeat_nested_in_repeat(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 2 times repeat 3 times - print 'hello'""") + print 'hello'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ for i in range(int('2')): for i in range(int('3')): print(f'''hello''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) @@ -1535,41 +1758,49 @@ def test_repeat_nested_in_repeat(self): # for list command # def test_for_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is 'hond', 'kat', 'papegaai' for dier in dieren - print dier""") + print dier""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ dieren = ['hond', 'kat', 'papegaai'] for dier in dieren: print(f'''{dier}''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( code=code, expected=expected, - expected_commands=['is', 'for', 'print'], - max_level=15 + expected_commands=["is", "for", "print"], + max_level=15, ) def test_for_list_multiline_body(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ familie is 'baby', 'mommy', 'daddy', 'grandpa', 'grandma' for shark in familie print shark ' shark tudutudutudu' print shark ' shark tudutudutudu' print shark ' shark tudutudutudu' - print shark ' shark'""") + print shark ' shark'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ familie = ['baby', 'mommy', 'daddy', 'grandpa', 'grandma'] for shark in familie: print(f'''{shark} shark tudutudutudu''') print(f'''{shark} shark tudutudutudu''') print(f'''{shark} shark tudutudutudu''') print(f'''{shark} shark''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=15) @@ -1577,71 +1808,89 @@ def test_for_list_multiline_body(self): # for loop # def test_for_loop(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 1 to 10 - a is i + 1""") - expected = textwrap.dedent("""\ + a is i + 1""" + ) + expected = textwrap.dedent( + """\ step = 1 if 1 < 10 else -1 for i in range(1, 10 + step, step): a = i + 1 - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( code=code, expected=expected, max_level=16, - expected_commands=['for', 'is', 'addition']) + expected_commands=["for", "is", "addition"], + ) def test_for_loop_with_int_vars(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ begin = 1 end = 10 for i in range begin to end - print i""") + print i""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ begin = 1 end = 10 step = 1 if begin < end else -1 for i in range(begin, end + step, step): print(f'''{i}''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_for_loop_multiline_body(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 2 b is 3 for a in range 2 to 4 a is a + 2 - b is b + 2""") + b is b + 2""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = 2 b = 3 step = 1 if 2 < 4 else -1 for a in range(2, 4 + step, step): a = a + 2 b = b + 2 - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_for_loop_followed_by_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 1 to 10 print i - print 'wie niet weg is is gezien'""") + print 'wie niet weg is is gezien'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if 1 < 10 else -1 for i in range(1, 10 + step, step): print(f'''{i}''') time.sleep(0.1) - print(f'''wie niet weg is is gezien''')""") + print(f'''wie niet weg is is gezien''')""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ 1 2 3 @@ -1652,75 +1901,86 @@ def test_for_loop_followed_by_print(self): 8 9 10 - wie niet weg is is gezien""") + wie niet weg is is gezien""" + ) self.multi_level_tester( code=code, expected=expected, max_level=16, - expected_commands=['for', 'print', 'print'], - output=output) + expected_commands=["for", "print", "print"], + output=output, + ) # issue 363 def test_for_loop_if_followed_by_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 0 to 10 antwoord is ask 'Wat is 5*5' if antwoord is 24 print 'fout' - print 'klaar met for loop'""") + print 'klaar met for loop'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ step = 1 if 0 < 10 else -1 for i in range(0, 10 + step, step):""", - (HedyTester.input_transpiled('antwoord', 'Wat is 5*5'), ' '), """\ + (HedyTester.input_transpiled("antwoord", "Wat is 5*5"), " "), + """\ if convert_numerals('Latin', antwoord) == convert_numerals('Latin', '24'): print(f'''fout''') time.sleep(0.1) - print(f'''klaar met for loop''')""") + print(f'''klaar met for loop''')""", + ) self.multi_level_tester(code=code, expected=expected, max_level=16) # issue 599 def test_for_loop_if(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 0 to 10 if i is 2 - print '2'""") + print '2'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if 0 < 10 else -1 for i in range(0, 10 + step, step): if convert_numerals('Latin', i) == convert_numerals('Latin', '2'): print(f'''2''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) # # arithmetic expressions tests # - @parameterized.expand([ - ('*', '*', '12'), - ('/', '/', '3.0'), - ('+', '+', '8'), - ('-', '-', '4')]) + @parameterized.expand( + [("*", "*", "12"), ("/", "/", "3.0"), ("+", "+", "8"), ("-", "-", "4")] + ) def test_int_calc(self, op, transpiled_op, output): code = f"print 6 {op} 2" expected = f"print(f'''{{6 {transpiled_op} 2}}''')" - self.multi_level_tester(code=code, expected=expected, output=output, max_level=17) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=17 + ) - @parameterized.expand([ - ('*', '*', '100'), - ('/', '/', '1.0'), - ('+', '+', '17'), - ('-', '-', '3')]) + @parameterized.expand( + [("*", "*", "100"), ("/", "/", "1.0"), ("+", "+", "17"), ("-", "-", "3")] + ) def test_nested_int_calc(self, op, transpiled_op, output): code = f"print 10 {op} 5 {op} 2" expected = f"print(f'''{{10 {transpiled_op} 5 {transpiled_op} 2}}''')" - self.multi_level_tester(code=code, expected=expected, output=output, max_level=17) + self.multi_level_tester( + code=code, expected=expected, output=output, max_level=17 + ) @parameterized.expand(HedyTester.arithmetic_operations) def test_float_calc(self, op): @@ -1744,117 +2004,148 @@ def test_print_float_calc_with_string(self, op): self.multi_level_tester(code=code, expected=expected, max_level=17) def test_print_add_negative_number(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ n = -4 +3 - print n""") - expected = textwrap.dedent("""\ + print n""" + ) + expected = textwrap.dedent( + """\ n = -4 + 3 - print(f'''{n}''')""") + print(f'''{n}''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) @parameterized.expand(HedyTester.arithmetic_operations) def test_float_calc_with_var(self, op): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ getal1 is 5 getal2 is 4.3 - print 'dat is dan: ' getal1 {op} getal2""") - expected = textwrap.dedent(f"""\ + print 'dat is dan: ' getal1 {op} getal2""" + ) + expected = textwrap.dedent( + f"""\ getal1 = 5 getal2 = 4.3 - print(f'''dat is dan: {{getal1 {op} getal2}}''')""") + print(f'''dat is dan: {{getal1 {op} getal2}}''')""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) @parameterized.expand(HedyTester.arithmetic_operations) def test_int_calc_with_var(self, op): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is 1 b is 2 - c is a {op} b""") - expected = textwrap.dedent(f"""\ + c is a {op} b""" + ) + expected = textwrap.dedent( + f"""\ a = 1 b = 2 - c = a {op} b""") + c = a {op} b""" + ) self.multi_level_tester(code=code, expected=expected, max_level=17) def test_concat_calc_with_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ getal1 is '5' getal2 is '6' getal3 is '7' - print 'dat is dan: ' getal1 + getal2 + getal3""") - expected = textwrap.dedent("""\ + print 'dat is dan: ' getal1 + getal2 + getal3""" + ) + expected = textwrap.dedent( + """\ getal1 = '5' getal2 = '6' getal3 = '7' - print(f'''dat is dan: {getal1 + getal2 + getal3}''')""") + print(f'''dat is dan: {getal1 + getal2 + getal3}''')""" + ) - check_output = (lambda x: HedyTester.run_code(x) == 'dat is dan: 567') + def check_output(x): + return HedyTester.run_code(x) == "dat is dan: 567" self.multi_level_tester( code=code, max_level=17, expected=expected, - extra_check_function=check_output + extra_check_function=check_output, ) def test_int_calc_chained_vars(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 5 b is a + 1 - print a + b""") + print a + b""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = 5 b = a + 1 - print(f'''{a + b}''')""") + print(f'''{a + b}''')""" + ) self.multi_level_tester( code=code, max_level=17, expected=expected, - extra_check_function=lambda x: self.run_code(x) == "11" + extra_check_function=lambda x: self.run_code(x) == "11", ) def test_calc_string_and_int_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ x is 'test1' - y is x + 1""") + y is x + 1""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidTypeCombinationException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidTypeCombinationException, + ) def test_concat_quoted_string_and_int_gives_type_error(self): code = """y is 'test1' + 1""" self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1, - exception=hedy.exceptions.InvalidTypeCombinationException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, + exception=hedy.exceptions.InvalidTypeCombinationException, + ) - @parameterized.expand(['-', '*', '/']) + @parameterized.expand(["-", "*", "/"]) def test_calc_with_single_quoted_strings_gives_type_error(self, operation): - code = textwrap.dedent(f"""\ - a is 1 {operation} 'Test'""") + code = textwrap.dedent( + f"""\ + a is 1 {operation} 'Test'""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) - @parameterized.expand(['-', '*', '/']) + @parameterized.expand(["-", "*", "/"]) def test_calc_with_double_quoted_strings_gives_type_error(self, operation): - code = textwrap.dedent(f"""\ - a is 1 {operation} "Test\"""") + code = textwrap.dedent( + f"""\ + a is 1 {operation} "Test\"""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 1, - exception=hedy.exceptions.InvalidArgumentTypeException) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 1, + exception=hedy.exceptions.InvalidArgumentTypeException, + ) # def test_access_variable_before_definition(self): # code = textwrap.dedent("""\ @@ -1880,7 +2171,8 @@ def test_calc_with_double_quoted_strings_gives_type_error(self, operation): # def test_list_with_spaces_nested_for_loop(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ actions is 'clap your hands', 'stomp your feet', 'shout Hurray' for action in actions for i in range 1 to 2 @@ -1888,8 +2180,10 @@ def test_list_with_spaces_nested_for_loop(self): print action print 'if youre happy and you know it and you really want to show it' print 'if youre happy and you know it' - print action""") - expected = textwrap.dedent("""\ + print action""" + ) + expected = textwrap.dedent( + """\ actions = ['clap your hands', 'stomp your feet', 'shout Hurray'] for action in actions: step = 1 if 1 < 2 else -1 @@ -1900,7 +2194,8 @@ def test_list_with_spaces_nested_for_loop(self): print(f'''if youre happy and you know it and you really want to show it''') print(f'''if youre happy and you know it''') print(f'''{action}''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=15) @@ -1909,15 +2204,18 @@ def test_list_with_spaces_nested_for_loop(self): # def test_if_pressed_with_list_and_for(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ lijstje is 'kip', 'haan', 'kuiken' if x is pressed for dier in lijstje print 'dier' else - print 'onbekend dier'""") + print 'onbekend dier'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ lijstje = ['kip', 'haan', 'kuiken'] pygame_end = False while not pygame_end: @@ -1936,37 +2234,42 @@ def test_if_pressed_with_list_and_for(self): # End of PyGame Event Handler else: print(f'''onbekend dier''') - break""") + break""" + ) - self.multi_level_tester( - code=code, - expected=expected, - max_level=15) + self.multi_level_tester(code=code, expected=expected, max_level=15) # # button tests # def test_button(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ x = 'knop' - x is button""") + x is button""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ x = 'knop' - create_button(x)""") + create_button(x)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=18) def test_if_button_is_pressed_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ x = 'PRINT' x is button if PRINT is pressed print 'The button got pressed!' else - print 'Other button is pressed!'""") + print 'Other button is pressed!'""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ x = 'PRINT' create_button(x) pygame_end = False @@ -1984,34 +2287,42 @@ def test_if_button_is_pressed_print(self): # End of PyGame Event Handler else: print(f'''Other button is pressed!''') - break""") + break""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_if_equality_make_button(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ x = 'knop1' if 'knop1' = x - x is button""") + x is button""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ x = 'knop1' if convert_numerals('Latin', 'knop1') == convert_numerals('Latin', x): - create_button(x)""") + create_button(x)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_if_button_is_pressed_print_in_repeat(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ x = 'but' x is button repeat 3 times if but is pressed print 'wow' else - print 'nah'""") + print 'nah'""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ x = 'but' create_button(x) for i in range(int('3')): @@ -2031,12 +2342,14 @@ def test_if_button_is_pressed_print_in_repeat(self): else: print(f'''nah''') break - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester(code=code, expected=expected, max_level=16) def test_simple_function(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ define simple_function_1 with parameter print "simple_function_1 - 1" m = "simple_function_1 - 2" @@ -2059,9 +2372,11 @@ def test_simple_function(self): a = "test1" call simple_function_3 with "A", a, 1.0 call simple_function_3 with "B", a, 1.0 - call simple_function_3 with "C", a, 1.0""") + call simple_function_3 with "C", a, 1.0""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ def simple_function_1(parameter): print(f'''simple_function_1 - 1''') m = 'simple_function_1 - 2' @@ -2084,9 +2399,11 @@ def simple_function_3(param_a, param_b, param_c): a = 'test1' simple_function_3('A', a, 1.0) simple_function_3('B', a, 1.0) - simple_function_3('C', a, 1.0)""") + simple_function_3('C', a, 1.0)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ simple_function_3 - 1 test1 simple_function_3 - 2 @@ -2094,17 +2411,16 @@ def simple_function_3(param_a, param_b, param_c): test1 simple_function_3 - 2 simple_function_3 - 2B - 1.0""") + 1.0""" + ) self.multi_level_tester( - code=code, - expected=expected, - output=output, - max_level=16 + code=code, expected=expected, output=output, max_level=16 ) def test_source_map(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ price = 0.0 food = ask 'What would you like to order?' drink = ask 'What would you like to drink?' @@ -2116,9 +2432,11 @@ def test_source_map(self): price = price + 1.20 if drink is 'soda' price = price + 2.35 - print 'That will be ' price ' dollar, please'""") + print 'That will be ' price ' dollar, please'""" + ) - expected_code = textwrap.dedent("""\ + expected_code = textwrap.dedent( + """\ price = 0.0 food = input(f'''What would you like to order?''') try: @@ -2144,53 +2462,56 @@ def test_source_map(self): price = price + 1.2 if convert_numerals('Latin', drink) == convert_numerals('Latin', 'soda'): price = price + 2.35 - print(f'''That will be {price} dollar, please''')""") + print(f'''That will be {price} dollar, please''')""" + ) expected_source_map = { - '1/1-1/6': '1/1-1/6', - '1/1-1/12': '1/1-1/12', - '2/1-2/5': '2/1-2/5', - '2/1-2/43': '2/1-9/9', - '3/1-3/6': '10/1-10/6', - '3/1-3/44': '10/1-17/9', - '4/4-4/8': '4/3-4/7', - '4/4-4/23': '18/4-18/77', - '5/5-5/10': '19/3-19/8', - '5/13-5/18': '19/11-19/16', - '5/5-5/25': '19/3-19/22', - '4/1-5/34': '18/1-19/22', - '6/4-6/8': '4/14-4/18', - '6/4-6/19': '20/4-20/73', - '7/5-7/10': '21/3-21/8', - '7/13-7/18': '21/11-21/16', - '7/5-7/25': '21/3-21/23', - '6/1-7/34': '20/1-21/23', - '8/4-8/9': '10/42-10/47', - '8/4-8/20': '22/4-22/74', - '9/5-9/10': '23/3-23/8', - '9/13-9/18': '23/11-23/16', - '9/5-9/25': '23/3-23/22', - '8/1-9/34': '22/1-23/22', - '10/4-10/9': '12/3-12/8', - '10/4-10/19': '24/4-24/73', - '11/5-11/10': '25/3-25/8', - '11/13-11/18': '25/11-25/16', - '11/5-11/25': '25/3-25/23', - '10/1-11/34': '24/1-25/23', - '12/23-12/28': '26/25-26/30', - '12/1-12/46': '26/1-26/50', - '1/1-12/47': '1/1-26/50' + "1/1-1/6": "1/1-1/6", + "1/1-1/12": "1/1-1/12", + "2/1-2/5": "2/1-2/5", + "2/1-2/43": "2/1-9/9", + "3/1-3/6": "10/1-10/6", + "3/1-3/44": "10/1-17/9", + "4/4-4/8": "4/3-4/7", + "4/4-4/23": "18/4-18/77", + "5/5-5/10": "19/3-19/8", + "5/13-5/18": "19/11-19/16", + "5/5-5/25": "19/3-19/22", + "4/1-5/34": "18/1-19/22", + "6/4-6/8": "4/14-4/18", + "6/4-6/19": "20/4-20/73", + "7/5-7/10": "21/3-21/8", + "7/13-7/18": "21/11-21/16", + "7/5-7/25": "21/3-21/23", + "6/1-7/34": "20/1-21/23", + "8/4-8/9": "10/42-10/47", + "8/4-8/20": "22/4-22/74", + "9/5-9/10": "23/3-23/8", + "9/13-9/18": "23/11-23/16", + "9/5-9/25": "23/3-23/22", + "8/1-9/34": "22/1-23/22", + "10/4-10/9": "12/3-12/8", + "10/4-10/19": "24/4-24/73", + "11/5-11/10": "25/3-25/8", + "11/13-11/18": "25/11-25/16", + "11/5-11/25": "25/3-25/23", + "10/1-11/34": "24/1-25/23", + "12/23-12/28": "26/25-26/30", + "12/1-12/46": "26/1-26/50", + "1/1-12/47": "1/1-26/50", } self.single_level_tester(code, expected=expected_code) self.source_map_tester(code=code, expected_source_map=expected_source_map) def test_nested_functions(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ define simple_function define nested_function print 1 - call simple_function""") + call simple_function""" + ) self.multi_level_tester( code=code, diff --git a/tests/test_level/test_level_13.py b/tests/test_level/test_level_13.py index 683c7cd0e4a..8de7c56a15e 100644 --- a/tests/test_level/test_level_13.py +++ b/tests/test_level/test_level_13.py @@ -1,4 +1,3 @@ - import textwrap from tests.Tester import HedyTester @@ -8,12 +7,15 @@ class TestsLevel13(HedyTester): level = 13 def test_and(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is ask 'hoe heet jij?' leeftijd is ask 'hoe oud ben jij?' if naam is 'Felienne' and leeftijd is 37 - print 'hallo jij!'""") - expected = textwrap.dedent("""\ + print 'hallo jij!'""" + ) + expected = textwrap.dedent( + """\ naam = input(f'''hoe heet jij?''') try: naam = int(naam) @@ -31,22 +33,22 @@ def test_and(self): except ValueError: pass if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Felienne') and convert_numerals('Latin', leeftijd) == convert_numerals('Latin', '37'): - print(f'''hallo jij!''')""") - - self.multi_level_tester( - max_level=16, - code=code, - expected=expected + print(f'''hallo jij!''')""" ) + self.multi_level_tester(max_level=16, code=code, expected=expected) + def test_equals(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ name = ask 'what is your name?' age = ask 'what is your age?' if name is 'Hedy' and age is 2 - print 'You are the real Hedy!'""") + print 'You are the real Hedy!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ name = input(f'''what is your name?''') try: name = int(name) @@ -64,32 +66,38 @@ def test_equals(self): except ValueError: pass if convert_numerals('Latin', name) == convert_numerals('Latin', 'Hedy') and convert_numerals('Latin', age) == convert_numerals('Latin', '2'): - print(f'''You are the real Hedy!''')""") + print(f'''You are the real Hedy!''')""" + ) self.multi_level_tester( code=code, max_level=16, expected=expected, - expected_commands=['ask', 'ask', 'if', 'and', 'print'] + expected_commands=["ask", "ask", "if", "and", "print"], ) def test_or(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if 5 is 5 or 4 is 4 - print 'hallo'""") - expected = textwrap.dedent("""\ + print 'hallo'""" + ) + expected = textwrap.dedent( + """\ if convert_numerals('Latin', '5') == convert_numerals('Latin', '5') or convert_numerals('Latin', '4') == convert_numerals('Latin', '4'): - print(f'''hallo''')""") + print(f'''hallo''')""" + ) self.multi_level_tester( code=code, max_level=16, expected=expected, - expected_commands=['if', 'or', 'print'] + expected_commands=["if", "or", "print"], ) def test_simple_function(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ define simple_function_1 with parameter print "simple_function_1 - 1" m = "simple_function_1 - 2" @@ -113,9 +121,11 @@ def test_simple_function(self): call simple_function_3 with "A", a, 1.0 call simple_function_3 with "B", a, 1.0 call simple_function_3 with "C", a, 1.0 - call simple_function_3 with "C", 3 + 3, 1.0""") + call simple_function_3 with "C", 3 + 3, 1.0""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ def simple_function_1(parameter): print(f'''simple_function_1 - 1''') m = 'simple_function_1 - 2' @@ -139,9 +149,11 @@ def simple_function_3(param_a, param_b, param_c): simple_function_3('A', a, 1.0) simple_function_3('B', a, 1.0) simple_function_3('C', a, 1.0) - simple_function_3('C', 3 + 3, 1.0)""") + simple_function_3('C', 3 + 3, 1.0)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ simple_function_3 - 1 test1 simple_function_3 - 1 @@ -151,11 +163,9 @@ def simple_function_3(param_a, param_b, param_c): 1.0 simple_function_3 - 2 simple_function_3 - 2B - 1.0""") + 1.0""" + ) self.multi_level_tester( - code=code, - expected=expected, - output=output, - max_level=16 + code=code, expected=expected, output=output, max_level=16 ) diff --git a/tests/test_level/test_level_14.py b/tests/test_level/test_level_14.py index 24e0634e3d6..176d5f37309 100644 --- a/tests/test_level/test_level_14.py +++ b/tests/test_level/test_level_14.py @@ -13,54 +13,65 @@ class TestsLevel14(HedyTester): @parameterized.expand(str(i) for i in range(10, 700, 10)) def test_greater_than_with_int_and_float(self, a): b = 7.0 - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ var = {a} if var > {b} print 'Above {b}' else - print 'Below'""") - expected = textwrap.dedent(f"""\ + print 'Below'""" + ) + expected = textwrap.dedent( + f"""\ var = {a} if convert_numerals('Latin', var)>convert_numerals('Latin', {b}): print(f'''Above {b}''') else: - print(f'''Below''')""") + print(f'''Below''')""" + ) self.multi_level_tester( code=code, max_level=16, expected=expected, - output=f'Above {b}', + output=f"Above {b}", ) @parameterized.expand(str(float(i)) for i in range(2, 16)) def test_not_greater_than_with_int_and_float(self, a): b = 15 - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ var = {a} if var > {b} print 'Above {b}' else - print 'Below'""") - expected = textwrap.dedent(f"""\ + print 'Below'""" + ) + expected = textwrap.dedent( + f"""\ var = {a} if convert_numerals('Latin', var)>convert_numerals('Latin', {b}): print(f'''Above {b}''') else: - print(f'''Below''')""") + print(f'''Below''')""" + ) self.multi_level_tester( code=code, expected=expected, max_level=16, - output='Below', + output="Below", ) @parameterized.expand(HedyTester.comparison_commands) def test_comparisons_with_int(self, comparison): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ leeftijd is ask 'Hoe oud ben jij?' if leeftijd {comparison} 12 - print 'Dan ben je jonger dan ik!'""") - expected = textwrap.dedent(f"""\ + print 'Dan ben je jonger dan ik!'""" + ) + expected = textwrap.dedent( + f"""\ leeftijd = input(f'''Hoe oud ben jij?''') try: leeftijd = int(leeftijd) @@ -70,7 +81,8 @@ def test_comparisons_with_int(self, comparison): except ValueError: pass if convert_numerals('Latin', leeftijd){comparison}convert_numerals('Latin', 12): - print(f'''Dan ben je jonger dan ik!''')""") + print(f'''Dan ben je jonger dan ik!''')""" + ) self.multi_level_tester( code=code, @@ -79,34 +91,39 @@ def test_comparisons_with_int(self, comparison): ) def test_equality_arabic(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ nummer1 is ٢ nummer2 is 2 if nummer1 != nummer2 print 'jahoor!' else - print 'neejoh!'""") + print 'neejoh!'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ nummer1 = 2 nummer2 = 2 if convert_numerals('Latin', nummer1)!=convert_numerals('Latin', nummer2): print(f'''jahoor!''') else: - print(f'''neejoh!''')""") + print(f'''neejoh!''')""" + ) self.multi_level_tester( - max_level=16, - code=code, - expected=expected, - output='neejoh!') + max_level=16, code=code, expected=expected, output="neejoh!" + ) def test_inequality_with_string(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ name is ask 'What is your name?' if name != 'Hedy' - print 'meh'""") - expected = textwrap.dedent(f"""\ + print 'meh'""" + ) + expected = textwrap.dedent( + f"""\ name = input(f'''What is your name?''') try: name = int(name) @@ -116,7 +133,8 @@ def test_inequality_with_string(self): except ValueError: pass if convert_numerals('Latin', name)!='Hedy': - print(f'''meh''')""") + print(f'''meh''')""" + ) self.multi_level_tester( code=code, @@ -125,13 +143,16 @@ def test_inequality_with_string(self): ) def test_inequality_Hindi(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ उम्र is ask 'आप कितने साल के हैं?' if उम्र > 12 print 'आप मुझसे छोटे हैं!' else - print 'आप मुझसे बड़े हैं!'""") - expected = textwrap.dedent(f"""\ + print 'आप मुझसे बड़े हैं!'""" + ) + expected = textwrap.dedent( + f"""\ उम्र = input(f'''आप कितने साल के हैं?''') try: उम्र = int(उम्र) @@ -143,7 +164,8 @@ def test_inequality_Hindi(self): if convert_numerals('Latin', उम्र)>convert_numerals('Latin', 12): print(f'''आप मुझसे छोटे हैं!''') else: - print(f'''आप मुझसे बड़े हैं!''')""") + print(f'''आप मुझसे बड़े हैं!''')""" + ) self.multi_level_tester( code=code, @@ -153,11 +175,14 @@ def test_inequality_Hindi(self): @parameterized.expand(HedyTester.equality_comparison_commands) def test_equality_with_string(self, comparison): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ name is ask 'What is your name?' if name {comparison} 'Hedy' - print 'meh'""") - expected = textwrap.dedent(f"""\ + print 'meh'""" + ) + expected = textwrap.dedent( + f"""\ name = input(f'''What is your name?''') try: name = int(name) @@ -167,7 +192,8 @@ def test_equality_with_string(self, comparison): except ValueError: pass if convert_numerals('Latin', name) == convert_numerals('Latin', 'Hedy'): - print(f'''meh''')""") + print(f'''meh''')""" + ) self.multi_level_tester( code=code, @@ -177,13 +203,16 @@ def test_equality_with_string(self, comparison): @parameterized.expand(HedyTester.comparison_commands) def test_comparisons_else(self, comparison): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ leeftijd is ask 'Hoe oud ben jij?' if leeftijd {comparison} 12 print 'Dan ben je jonger dan ik!' else - print 'Dan ben je ouder dan ik!'""") - expected = textwrap.dedent(f"""\ + print 'Dan ben je ouder dan ik!'""" + ) + expected = textwrap.dedent( + f"""\ leeftijd = input(f'''Hoe oud ben jij?''') try: leeftijd = int(leeftijd) @@ -195,21 +224,21 @@ def test_comparisons_else(self, comparison): if convert_numerals('Latin', leeftijd){comparison}convert_numerals('Latin', 12): print(f'''Dan ben je jonger dan ik!''') else: - print(f'''Dan ben je ouder dan ik!''')""") - - self.multi_level_tester( - code=code, - max_level=16, - expected=expected + print(f'''Dan ben je ouder dan ik!''')""" ) + self.multi_level_tester(code=code, max_level=16, expected=expected) + @parameterized.expand(HedyTester.comparison_commands) def tests_smaller_no_spaces(self, comparison): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ leeftijd is ask 'Hoe oud ben jij?' if leeftijd {comparison} 12 - print 'Dan ben je jonger dan ik!'""") - expected = textwrap.dedent(f"""\ + print 'Dan ben je jonger dan ik!'""" + ) + expected = textwrap.dedent( + f"""\ leeftijd = input(f'''Hoe oud ben jij?''') try: leeftijd = int(leeftijd) @@ -219,149 +248,159 @@ def tests_smaller_no_spaces(self, comparison): except ValueError: pass if convert_numerals('Latin', leeftijd){comparison}convert_numerals('Latin', 12): - print(f'''Dan ben je jonger dan ik!''')""") - - self.multi_level_tester( - code=code, - max_level=16, - expected=expected + print(f'''Dan ben je jonger dan ik!''')""" ) + self.multi_level_tester(code=code, max_level=16, expected=expected) + @parameterized.expand(HedyTester.number_comparison_commands) def test_comparison_with_string_gives_type_error(self, comparison): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is 'text' if a {comparison} 12 - b is 1""") + b is 1""" + ) self.multi_level_tester( code=code, max_level=16, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) @parameterized.expand(HedyTester.number_comparison_commands) def test_comparison_with_list_gives_type_error(self, comparison): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is 1, 2, 3 if a {comparison} 12 - b is 1""") + b is 1""" + ) self.multi_level_tester( code=code, max_level=15, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_not_equal_promotes_int_to_float(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is 1 b is 1.2 if a != b - b is 1""") + b is 1""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ a = 1 b = 1.2 if convert_numerals('Latin', a)!=convert_numerals('Latin', b): - b = 1""") - - self.multi_level_tester( - code=code, - max_level=16, - expected=expected + b = 1""" ) - @parameterized.expand([ - ('"text"', "'text'"), - ("'text'", "'text'"), - ('1', '1'), - ('1.3', '1.3'), - ('1, 2', '[1, 2]')]) + self.multi_level_tester(code=code, max_level=16, expected=expected) + + @parameterized.expand( + [ + ('"text"', "'text'"), + ("'text'", "'text'"), + ("1", "1"), + ("1.3", "1.3"), + ("1, 2", "[1, 2]"), + ] + ) def test_not_equal(self, arg, exp): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is {arg} b is {arg} if a != b - b is 1""") + b is 1""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ a = {exp} b = {exp} if convert_numerals('Latin', a)!=convert_numerals('Latin', b): - b = 1""") - - self.multi_level_tester( - code=code, - max_level=15, - expected=expected + b = 1""" ) + self.multi_level_tester(code=code, max_level=15, expected=expected) + def test_if_with_double_equals(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam = 'Hedy' if naam == Hedy - print 'koekoek'""") + print 'koekoek'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'''koekoek''')""") + print(f'''koekoek''')""" + ) - self.multi_level_tester( - code=code, - expected=expected, - max_level=16) + self.multi_level_tester(code=code, expected=expected, max_level=16) @parameterized.expand(HedyTester.equality_comparison_commands) def test_equality_with_lists(self, comparison): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a = 1, 2 b = 1, 2 if a {comparison} b - sleep""") + sleep""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ a = [1, 2] b = [1, 2] if convert_numerals('Latin', a) == convert_numerals('Latin', b): - time.sleep(1)""") + time.sleep(1)""" + ) - self.multi_level_tester( - code=code, - expected=expected, - max_level=15) + self.multi_level_tester(code=code, expected=expected, max_level=15) def test_inequality_with_lists(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a = 1, 2 b = 1, 2 if a != b - sleep""") + sleep""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = [1, 2] b = [1, 2] if convert_numerals('Latin', a)!=convert_numerals('Latin', b): - time.sleep(1)""") + time.sleep(1)""" + ) - self.multi_level_tester( - code=code, - expected=expected, - max_level=15) + self.multi_level_tester(code=code, expected=expected, max_level=15) @parameterized.expand(HedyTester.comparison_commands) def test_comparisons_with_boolean(self, comparison): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ leeftijd is ask 'Hoe oud ben jij?' if leeftijd {comparison} 12 or leeftijd {comparison} 15 print 'Dan ben je jonger dan ik!' if leeftijd {comparison} 12 and leeftijd {comparison} 15 - print 'Some other string!'""") + print 'Some other string!'""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ leeftijd = input(f'''Hoe oud ben jij?''') try: leeftijd = int(leeftijd) @@ -375,7 +414,8 @@ def test_comparisons_with_boolean(self, comparison): print(f'''Dan ben je jonger dan ik!''') if convert_numerals('Latin', leeftijd){comparison}convert_numerals('Latin', 12)\ and convert_numerals('Latin', leeftijd){comparison}convert_numerals('Latin', 15): - print(f'''Some other string!''')""") + print(f'''Some other string!''')""" + ) self.multi_level_tester( code=code, @@ -383,42 +423,48 @@ def test_comparisons_with_boolean(self, comparison): expected=expected, ) - @parameterized.expand([ - ('"text"', '1'), # double-quoted text and number - ("'text'", '1'), # single-quoted text and number - ('1, 2', '1'), # list and number - ('1, 2', "'text'"), # list and single-quoted text - ('1, 2', '"text"')]) # list and double-quoted text + @parameterized.expand( + [ + ('"text"', "1"), # double-quoted text and number + ("'text'", "1"), # single-quoted text and number + ("1, 2", "1"), # list and number + ("1, 2", "'text'"), # list and single-quoted text + ("1, 2", '"text"'), + ] + ) # list and double-quoted text def test_not_equal_with_diff_types_gives_error(self, left, right): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is {left} b is {right} if a != b - b is 1""") + b is 1""" + ) self.multi_level_tester( code=code, max_level=15, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=exceptions.InvalidTypeCombinationException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=exceptions.InvalidTypeCombinationException, ) def test_missing_indent_else(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ age = ask 'How old are you?' if age < 13 print 'You are younger than me!' else - print 'You are older than me!'""") + print 'You are older than me!'""" + ) self.multi_level_tester( - code=code, - max_level=15, - exception=exceptions.NoIndentationException + code=code, max_level=15, exception=exceptions.NoIndentationException ) def test_simple_function(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ define test_function_1 int = 1 return "Test function " int @@ -449,9 +495,11 @@ def test_simple_function(self): print "" call test_function_3 with 5 print "" - call test_function_3 with 6""") + call test_function_3 with 6""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ def test_function_1(): _int = 1 return f'''Test function {_int}''' @@ -482,9 +530,11 @@ def test_function_3(_input): print(f'''''') test_function_3(5) print(f'''''') - test_function_3(6)""") + test_function_3(6)""" + ) - output = textwrap.dedent("""\ + output = textwrap.dedent( + """\ Test function 1 Test function 2 Test function 3 @@ -502,24 +552,25 @@ def test_function_3(_input): NE5 GT5 - GTE5""") + GTE5""" + ) self.multi_level_tester( - code=code, - expected=expected, - output=output, - max_level=16 + code=code, expected=expected, output=output, max_level=16 ) def test_source_map(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ age = ask 'How old are you?' if age < 13 print 'You are younger than me!' else - print 'You are older than me!'""") + print 'You are older than me!'""" + ) - excepted_code = textwrap.dedent("""\ + excepted_code = textwrap.dedent( + """\ age = input(f'''How old are you?''') try: age = int(age) @@ -531,19 +582,20 @@ def test_source_map(self): if convert_numerals('Latin', age) 5 {op} answer < 10 answer = ask 'What is 5 times 5?' - print 'A correct answer has been given'""") + print 'A correct answer has been given'""" + ) # Splitting like this to wrap the line around 120 characters max - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ answer = 7 while convert_numerals('Latin', answer)>convert_numerals('Latin', 5) {op} convert_numerals('Latin', answer) int(1), see note in 4047 - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ l = [1, 2] x = 3 - l[1] = x""") + l[1] = x""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ l = [1, 2] x = 3""", - HedyTester.list_access_transpiled('l[1-1]'), - "l[1-1] = x") + HedyTester.list_access_transpiled("l[1-1]"), + "l[1-1] = x", + ) self.single_level_tester(code=code, expected=expected) def test_change_list_item_number(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ l = [1, 2] m = 2 - l[m] = 3""") + l[m] = 3""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ l = [1, 2] m = 2""", - HedyTester.list_access_transpiled('l[m-1]'), - "l[m-1] = 3") + HedyTester.list_access_transpiled("l[m-1]"), + "l[m-1] = 3", + ) self.single_level_tester(code=code, expected=expected) def test_equality_with_number_and_list_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is [5, 6, 7] if 1 is color - print 'success!'""") + print 'success!'""" + ) self.multi_level_tester( code=code, exception=hedy.exceptions.InvalidTypeCombinationException, max_level=16, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2 + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, ) - @parameterized.expand(["'text'", '1', '1.3', '[1, 2]']) + @parameterized.expand(["'text'", "1", "1.3", "[1, 2]"]) def test_not_equal(self, arg): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a = {arg} b = {arg} if a != b - b = 1""") + b = 1""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ a = {arg} b = {arg} if convert_numerals('Latin', a)!=convert_numerals('Latin', b): - b = 1""") + b = 1""" + ) self.single_level_tester(code, expected=expected) - @parameterized.expand([ - ('"text"', '1'), # double-quoted text and number - ("'text'", '1'), # single-quoted text and number - ('[1, 2]', '1'), # list and number - ('[1, 2]', "'text'")]) # list and text + @parameterized.expand( + [ + ('"text"', "1"), # double-quoted text and number + ("'text'", "1"), # single-quoted text and number + ("[1, 2]", "1"), # list and number + ("[1, 2]", "'text'"), + ] + ) # list and text def test_not_equal_with_diff_types_gives_error(self, left, right): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a = {left} b = {right} if a != b - b = 1""") + b = 1""" + ) - self.single_level_tester(code, exception=exceptions.InvalidTypeCombinationException) + self.single_level_tester( + code, exception=exceptions.InvalidTypeCombinationException + ) def test_color_with_list_variable_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ c = ['red', 'green', 'blue'] - color c""") + color c""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_color_with_list_access_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ colors = ['red', 'green', 'blue'] - color colors[random]""") + color colors[random]""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ colors = ['red', 'green', 'blue']""", - HedyTester.turtle_color_command_transpiled('{random.choice(colors)}')) + HedyTester.turtle_color_command_transpiled("{random.choice(colors)}"), + ) self.multi_level_tester( code=code, @@ -541,25 +624,31 @@ def test_color_with_list_access_random(self): # forward tests # def test_forward_with_list_variable_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a = [1, 2, 3] - forward a""") + forward a""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_forward_with_list_access_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ directions = [10, 100, 360] - forward directions[random]""") + forward directions[random]""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ directions = [10, 100, 360]""", - HedyTester.list_access_transpiled('random.choice(directions)'), - HedyTester.forward_transpiled('random.choice(directions)', self.level)) + HedyTester.list_access_transpiled("random.choice(directions)"), + HedyTester.forward_transpiled("random.choice(directions)", self.level), + ) self.multi_level_tester( code=code, @@ -571,25 +660,31 @@ def test_forward_with_list_access_random(self): # turn tests # def test_turn_with_list_variable_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a = [45, 90, 180] - turn a""") + turn a""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_turn_with_list_access_random(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ directions = [10, 100, 360] - turn directions[random]""") + turn directions[random]""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ directions = [10, 100, 360]""", - HedyTester.list_access_transpiled('random.choice(directions)'), - HedyTester.turn_transpiled('random.choice(directions)', self.level)) + HedyTester.list_access_transpiled("random.choice(directions)"), + HedyTester.turn_transpiled("random.choice(directions)", self.level), + ) self.multi_level_tester( code=code, @@ -598,13 +693,16 @@ def test_turn_with_list_access_random(self): ) def test_if_pressed_with_list_and_for(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ lijstje is ['kip', 'haan', 'kuiken'] if x is pressed for dier in lijstje - print 'dier'""") + print 'dier'""" + ) - expected = HedyTester.dedent("""\ + expected = HedyTester.dedent( + """\ lijstje = ['kip', 'haan', 'kuiken'] pygame_end = False while not pygame_end: @@ -620,28 +718,39 @@ def test_if_pressed_with_list_and_for(self): print(f'''dier''') time.sleep(0.1) break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.single_level_tester(code=code, expected=expected) - @parameterized.expand(['number is', 'print', 'forward', 'turn']) + @parameterized.expand(["number is", "print", "forward", "turn"]) def test_at_random_express(self, command): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ numbers is [1, 2, 3] - {command} numbers at random""") - self.single_level_tester(code=code, exception=hedy.exceptions.InvalidAtCommandException) + {command} numbers at random""" + ) + self.single_level_tester( + code=code, exception=hedy.exceptions.InvalidAtCommandException + ) def test_at_random_express_sleep(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ numbers is [1, 2, 3] - sleep numbers at random""") + sleep numbers at random""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ numbers = [1, 2, 3] - pass""") + pass""" + ) skipped_mappings = [ - SkippedMapping(SourceRange(2, 1, 2, 24), hedy.exceptions.InvalidAtCommandException), + SkippedMapping( + SourceRange(2, 1, 2, 24), hedy.exceptions.InvalidAtCommandException + ), ] self.single_level_tester( diff --git a/tests/test_level/test_level_17.py b/tests/test_level/test_level_17.py index d6459967e04..f4fec11e658 100644 --- a/tests/test_level/test_level_17.py +++ b/tests/test_level/test_level_17.py @@ -11,41 +11,52 @@ class TestsLevel17(HedyTester): level = 17 def test_if_with_indent(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 'Hedy' if naam is 'Hedy': - print 'koekoek'""") - expected = textwrap.dedent("""\ + print 'koekoek'""" + ) + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'''koekoek''')""") + print(f'''koekoek''')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_with_equals_sign(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 'Hedy' if naam == Hedy: - print 'koekoek'""") + print 'koekoek'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'''koekoek''')""") + print(f'''koekoek''')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord is ask 'Hoeveel is 10 plus 10?' if antwoord is 20: print 'Goedzo!' print 'Het antwoord was inderdaad ' antwoord else: print 'Foutje' - print 'Het antwoord moest zijn ' antwoord""") + print 'Het antwoord moest zijn ' antwoord""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ antwoord = input(f'''Hoeveel is 10 plus 10?''') try: antwoord = int(antwoord) @@ -59,49 +70,58 @@ def test_if_else(self): print(f'''Het antwoord was inderdaad {antwoord}''') else: print(f'''Foutje''') - print(f'''Het antwoord moest zijn {antwoord}''')""") + print(f'''Het antwoord moest zijn {antwoord}''')""" + ) self.single_level_tester(code=code, expected=expected) def test_if_else_boolean(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ computerc = 'PC' userc = 'Hedy' print 'Pilihan komputer: ' computerc if userc is computerc and userc is 'Hedy': print 'SERI' else: - print 'Komputer'""") + print 'Komputer'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ computerc = 'PC' userc = 'Hedy' print(f'''Pilihan komputer: {computerc}''') if convert_numerals('Latin', userc) == convert_numerals('Latin', computerc) and convert_numerals('Latin', userc) == convert_numerals('Latin', 'Hedy'): print(f'''SERI''') else: - print(f'''Komputer''')""") + print(f'''Komputer''')""" + ) self.single_level_tester(code=code, expected=expected) def test_for_loop_arabic(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for دورة in range ١ to ٥: - print دورة""") + print دورة""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if 1 < 5 else -1 for دورة in range(1, 5 + step, step): print(f'''{دورة}''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester( - code=code, - expected=expected, - expected_commands=['for', 'print']) + code=code, expected=expected, expected_commands=["for", "print"] + ) def test_if_elif_boolean(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ computerc = 'PC' userc = 'Hedy' print 'Pilihan komputer: ' computerc @@ -110,9 +130,11 @@ def test_if_elif_boolean(self): elif userc is 'PC' and userc is 'Hedy': print 'HARI' else: - print 'Komputer'""") + print 'Komputer'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ computerc = 'PC' userc = 'Hedy' print(f'''Pilihan komputer: {computerc}''') @@ -121,114 +143,132 @@ def test_if_elif_boolean(self): elif convert_numerals('Latin', userc) == convert_numerals('Latin', 'PC') and convert_numerals('Latin', userc) == convert_numerals('Latin', 'Hedy'): print(f'''HARI''') else: - print(f'''Komputer''')""") + print(f'''Komputer''')""" + ) self.single_level_tester(code=code, expected=expected) def test_for_loop(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 2 b is 3 for a in range 2 to 4: a is a + 2 - b is b + 2""") - expected = textwrap.dedent("""\ + b is b + 2""" + ) + expected = textwrap.dedent( + """\ a = 2 b = 3 step = 1 if 2 < 4 else -1 for a in range(2, 4 + step, step): a = a + 2 b = b + 2 - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester(code=code, expected=expected) def test_if__else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 5 if a is 1: x is 2 else: - x is 222""") - expected = textwrap.dedent("""\ + x is 222""" + ) + expected = textwrap.dedent( + """\ a = 5 if convert_numerals('Latin', a) == convert_numerals('Latin', '1'): x = 2 else: - x = 222""") + x = 222""" + ) self.single_level_tester(code=code, expected=expected) def test_forloop(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 1 to 10: print i - print 'wie niet weg is is gezien'""") - expected = textwrap.dedent("""\ + print 'wie niet weg is is gezien'""" + ) + expected = textwrap.dedent( + """\ step = 1 if 1 < 10 else -1 for i in range(1, 10 + step, step): print(f'''{i}''') time.sleep(0.1) - print(f'''wie niet weg is is gezien''')""") + print(f'''wie niet weg is is gezien''')""" + ) self.single_level_tester(code=code, expected=expected) def test_allow_space_after_else_line(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 1 if a is 1: print a else: - print 'nee'""") + print 'nee'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = 1 if convert_numerals('Latin', a) == convert_numerals('Latin', '1'): print(f'''{a}''') else: - print(f'''nee''')""") + print(f'''nee''')""" + ) self.multi_level_tester( max_level=17, code=code, expected=expected, - expected_commands=['is', 'if', 'print', 'print'] + expected_commands=["is", "if", "print", "print"], ) def test_while_undefined_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ while antwoord != 25: - print 'hoera'""") + print 'hoera'""" + ) self.single_level_tester( - code=code, - exception=hedy.exceptions.UndefinedVarException + code=code, exception=hedy.exceptions.UndefinedVarException ) def test_allow_space_before_colon(self): - - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 1 if a is 1 : print a else: - print 'nee'""") + print 'nee'""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ a = 1 if convert_numerals('Latin', a) == convert_numerals('Latin', '1'): print(f'''{a}''') else: - print(f'''nee''')""") - - self.multi_level_tester( - code=code, - max_level=17, - expected=expected + print(f'''nee''')""" ) + self.multi_level_tester(code=code, max_level=17, expected=expected) + def test_if_under_else_in_for(self): # todo can me multitester with higher levels! - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 0 to 10: antwoord is ask 'Wat is 5*5' if antwoord is 24: @@ -236,9 +276,11 @@ def test_if_under_else_in_for(self): else: print 'Dat is goed!' if antwoord is 25: - i is 10""") + i is 10""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if 0 < 10 else -1 for i in range(0, 10 + step, step): antwoord = input(f'''Wat is 5*5''') @@ -255,112 +297,135 @@ def test_if_under_else_in_for(self): print(f'''Dat is goed!''') if convert_numerals('Latin', antwoord) == convert_numerals('Latin', '25'): i = 10 - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester(code=code, expected=expected) def test_if_elif(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 5 if a is 1: x is 2 elif a is 2: - x is 222""") - expected = textwrap.dedent("""\ + x is 222""" + ) + expected = textwrap.dedent( + """\ a = 5 if convert_numerals('Latin', a) == convert_numerals('Latin', '1'): x = 2 elif convert_numerals('Latin', a) == convert_numerals('Latin', '2'): - x = 222""") + x = 222""" + ) self.single_level_tester(code=code, expected=expected) def test_if_elif_french(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a est 5 si a est 1: x est 2 sinon si a est 2: - x est 222""") - expected = textwrap.dedent("""\ + x est 222""" + ) + expected = textwrap.dedent( + """\ a = 5 if convert_numerals('Latin', a) == convert_numerals('Latin', '1'): x = 2 elif convert_numerals('Latin', a) == convert_numerals('Latin', '2'): - x = 222""") + x = 222""" + ) - self.single_level_tester(code=code, expected=expected, lang='fr') + self.single_level_tester(code=code, expected=expected, lang="fr") def test_if_with_multiple_elifs(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 5 if a is 1: x is 2 elif a is 4: x is 3 elif a is 2: - x is 222""") - expected = textwrap.dedent("""\ + x is 222""" + ) + expected = textwrap.dedent( + """\ a = 5 if convert_numerals('Latin', a) == convert_numerals('Latin', '1'): x = 2 elif convert_numerals('Latin', a) == convert_numerals('Latin', '4'): x = 3 elif convert_numerals('Latin', a) == convert_numerals('Latin', '2'): - x = 222""") + x = 222""" + ) self.single_level_tester( - code=code, expected=expected, expected_commands=[ - 'is', 'if', 'is', 'elif', 'is', 'elif', 'is']) + code=code, + expected=expected, + expected_commands=["is", "if", "is", "elif", "is", "elif", "is"], + ) def test_if_in_list_with_string_var_gives_type_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ items is 'red' if 'red' in items: - a is 1""") + a is 1""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_equality_with_lists(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ m is [1, 2] n is [1, 2] if m is n: - a is 1""") + a is 1""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ m = [1, 2] n = [1, 2] if convert_numerals('Latin', m) == convert_numerals('Latin', n): - a = 1""") - - self.multi_level_tester( - code=code, - expected=expected + a = 1""" ) + self.multi_level_tester(code=code, expected=expected) + def test_equality_with_incompatible_types_gives_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 'test' b is 15 if a is b: - c is 1""") + c is 1""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=hedy.exceptions.InvalidTypeCombinationException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=hedy.exceptions.InvalidTypeCombinationException, ) @parameterized.expand(HedyTester.comparison_commands) def test_comparisons(self, comparison): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ leeftijd is ask 'Hoe oud ben jij?' if leeftijd {comparison} 12: - print 'Dan ben je jonger dan ik!'""") - expected = textwrap.dedent(f"""\ + print 'Dan ben je jonger dan ik!'""" + ) + expected = textwrap.dedent( + f"""\ leeftijd = input(f'''Hoe oud ben jij?''') try: leeftijd = int(leeftijd) @@ -370,78 +435,91 @@ def test_comparisons(self, comparison): except ValueError: pass if convert_numerals('Latin', leeftijd){comparison}convert_numerals('Latin', 12): - print(f'''Dan ben je jonger dan ik!''')""") + print(f'''Dan ben je jonger dan ik!''')""" + ) self.single_level_tester(code=code, expected=expected) @parameterized.expand(HedyTester.number_comparison_commands) def test_smaller_with_string_gives_type_error(self, comparison): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is 'text' if a {comparison} 12: - b is 1""") + b is 1""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2, - exception=hedy.exceptions.InvalidArgumentTypeException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2, + exception=hedy.exceptions.InvalidArgumentTypeException, ) def test_not_equal_string_literal(self): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ if 'quoted' != 'string': - sleep""") - expected = textwrap.dedent(f"""\ + sleep""" + ) + expected = textwrap.dedent( + f"""\ if 'quoted'!='string': - time.sleep(1)""") - - self.multi_level_tester( - code=code, - expected=expected + time.sleep(1)""" ) - @parameterized.expand(["'text'", '1', '1.3', '[1, 2]']) + self.multi_level_tester(code=code, expected=expected) + + @parameterized.expand(["'text'", "1", "1.3", "[1, 2]"]) def test_not_equal(self, arg): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a = {arg} b = {arg} if a != b: - b = 1""") + b = 1""" + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ a = {arg} b = {arg} if convert_numerals('Latin', a)!=convert_numerals('Latin', b): - b = 1""") - - self.multi_level_tester( - code=code, - expected=expected + b = 1""" ) - @parameterized.expand([ - ("'text'", '1'), # text and number - ('[1, 2]', '1'), # list and number - ('[1, 2]', "'text'")]) # list and text + self.multi_level_tester(code=code, expected=expected) + + @parameterized.expand( + [ + ("'text'", "1"), # text and number + ("[1, 2]", "1"), # list and number + ("[1, 2]", "'text'"), + ] + ) # list and text def test_not_equal_with_diff_types_gives_error(self, left, right): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a = {left} b = {right} if a != b: - b = 1""") + b = 1""" + ) self.multi_level_tester( code=code, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 3, - exception=exceptions.InvalidTypeCombinationException + extra_check_function=lambda c: c.exception.arguments["line_number"] == 3, + exception=exceptions.InvalidTypeCombinationException, ) def test_if_pressed_with_turtlecolor(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed: - color red""") + color red""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ pygame_end = False while not pygame_end: pygame.display.update() @@ -457,33 +535,38 @@ def test_if_pressed_with_turtlecolor(self): 12, True) } break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.multi_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_turtle() + code=code, expected=expected, extra_check_function=self.is_turtle() ) def test_if_no_colon_after_pressed_gives_parse_error(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if x is pressed - print 'no colon!'""") + print 'no colon!'""" + ) self.single_level_tester( code=code, exception=hedy.exceptions.ParseException, - extra_check_function=lambda c: c.exception.error_location[0] == 2 and c.exception.error_location[1] == 5 + extra_check_function=lambda c: c.exception.error_location[0] == 2 + and c.exception.error_location[1] == 5, ) def test_if_button_is_pressed_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ x = 'PRINT' x is button if PRINT is pressed: - print 'The button got pressed!'""") + print 'The button got pressed!'""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ x = 'PRINT' create_button(x) pygame_end = False @@ -498,20 +581,24 @@ def test_if_button_is_pressed_print(self): if event.key == 'PRINT': print(f'''The button got pressed!''') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.single_level_tester(code=code, expected=expected) def test_pressed_elif(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if a is pressed: print 'A' elif b is pressed: print 'B' else: - print 'Other'""") + print 'Other'""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ pygame_end = False while not pygame_end: pygame.display.update() @@ -532,39 +619,48 @@ def test_pressed_elif(self): # End of PyGame Event Handler else: print(f'''Other''') - break""") + break""" + ) self.single_level_tester(code=code, expected=expected) def test_nested_functions(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ define simple_function: define nested_function: print 1 - call simple_function""") + call simple_function""" + ) - self.single_level_tester(code=code, exception=hedy.exceptions.NestedFunctionException) + self.single_level_tester( + code=code, exception=hedy.exceptions.NestedFunctionException + ) def test_source_map(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 1 to 10: print i - print 'Ready or not, here I come!'""") + print 'Ready or not, here I come!'""" + ) - excepted_code = textwrap.dedent("""\ + excepted_code = textwrap.dedent( + """\ step = 1 if 1 < 10 else -1 for i in range(1, 10 + step, step): print(f'''{i}''') time.sleep(0.1) - print(f'''Ready or not, here I come!''')""") + print(f'''Ready or not, here I come!''')""" + ) expected_source_map = { - '1/5-1/6': '1/10-1/11', - '2/11-2/12': '2/5-2/6', - '2/5-2/12': '3/3-3/20', - '1/1-2/21': '1/1-4/18', - '3/1-3/35': '5/1-5/41', - '1/1-3/36': '1/1-5/41' + "1/5-1/6": "1/10-1/11", + "2/11-2/12": "2/5-2/6", + "2/5-2/12": "3/3-3/20", + "1/1-2/21": "1/1-4/18", + "3/1-3/35": "5/1-5/41", + "1/1-3/36": "1/1-5/41", } self.single_level_tester(code, expected=excepted_code) diff --git a/tests/test_level/test_level_18.py b/tests/test_level/test_level_18.py index ef1c56a1cc7..b4221419b58 100644 --- a/tests/test_level/test_level_18.py +++ b/tests/test_level/test_level_18.py @@ -9,50 +9,58 @@ class TestsLevel18(HedyTester): level = 18 - @parameterized.expand([['(', ')'], ['(', ')']]) + @parameterized.expand([["(", ")"], ["(", ")"]]) def test_print_brackets(self, bracket_open, bracket_close): - code = textwrap.dedent(f"""\ - print{bracket_open}'Hallo!'{bracket_close}""") + code = textwrap.dedent( + f"""\ + print{bracket_open}'Hallo!'{bracket_close}""" + ) - expected = textwrap.dedent("""\ - print(f'''Hallo!''')""") + expected = textwrap.dedent( + """\ + print(f'''Hallo!''')""" + ) self.multi_level_tester( code=code, expected=expected, extra_check_function=self.is_not_turtle(), - expected_commands=['print'] + expected_commands=["print"], ) def test_print_var_brackets(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 'Hedy' - print('ik heet', naam)""") + print('ik heet', naam)""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' - print(f'''ik heet{naam}''')""") + print(f'''ik heet{naam}''')""" + ) self.multi_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_not_turtle() + code=code, expected=expected, extra_check_function=self.is_not_turtle() ) def test_print_comma(self): code = "print('ik heet ,')" expected = "print(f'''ik heet ,''')" self.multi_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_not_turtle()) + code=code, expected=expected, extra_check_function=self.is_not_turtle() + ) - @parameterized.expand(['=', 'is']) + @parameterized.expand(["=", "is"]) def test_input(self, assigment): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ leeftijd {assigment} input('Hoe oud ben jij?') - print(leeftijd)""") - expected = textwrap.dedent("""\ + print(leeftijd)""" + ) + expected = textwrap.dedent( + """\ leeftijd = input(f'''Hoe oud ben jij?''') try: leeftijd = int(leeftijd) @@ -61,26 +69,31 @@ def test_input(self, assigment): leeftijd = float(leeftijd) except ValueError: pass - print(f'''{leeftijd}''')""") + print(f'''{leeftijd}''')""" + ) self.multi_level_tester( max_level=20, code=code, expected=expected, - extra_check_function=self.is_not_turtle() + extra_check_function=self.is_not_turtle(), ) - @parameterized.expand([':', ':']) + @parameterized.expand([":", ":"]) def test_if_with_dequals_sign_colon(self, colon): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ naam is 'Hedy' if naam == Hedy{colon} - print('koekoek')""") + print('koekoek')""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ naam = 'Hedy' if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'): - print(f'''koekoek''')""") + print(f'''koekoek''')""" + ) self.single_level_tester(code=code, expected=expected) @@ -124,16 +137,19 @@ def test_if_with_dequals_sign_colon(self, colon): # ) def test_if_else(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ antwoord is input('Hoeveel is 10 plus 10?') if antwoord is 20: print('Goedzo!') print('Het antwoord was inderdaad', antwoord) else: print('Foutje') - print('Het antwoord moest zijn', antwoord)""") + print('Het antwoord moest zijn', antwoord)""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ antwoord = input(f'''Hoeveel is 10 plus 10?''') try: antwoord = int(antwoord) @@ -147,78 +163,89 @@ def test_if_else(self): print(f'''Het antwoord was inderdaad{antwoord}''') else: print(f'''Foutje''') - print(f'''Het antwoord moest zijn{antwoord}''')""") + print(f'''Het antwoord moest zijn{antwoord}''')""" + ) self.multi_level_tester( code=code, expected=expected, - expected_commands=['input', 'if', 'print', 'print', 'print', 'print'], - extra_check_function=self.is_not_turtle() + expected_commands=["input", "if", "print", "print", "print", "print"], + extra_check_function=self.is_not_turtle(), ) def test_for_loop(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ a is 2 b is 3 for a in range(2, 4): a is a + 2 - b is b + 2""") - expected = textwrap.dedent("""\ + b is b + 2""" + ) + expected = textwrap.dedent( + """\ a = 2 b = 3 step = 1 if 2 < 4 else -1 for a in range(2, 4 + step, step): a = a + 2 b = b + 2 - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_not_turtle() + code=code, expected=expected, extra_check_function=self.is_not_turtle() ) def test_for_nesting(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range(1, 3): for j in range(1, 4): - print('rondje: ', i, ' tel: ', j)""") - expected = textwrap.dedent("""\ + print('rondje: ', i, ' tel: ', j)""" + ) + expected = textwrap.dedent( + """\ step = 1 if 1 < 3 else -1 for i in range(1, 3 + step, step): step = 1 if 1 < 4 else -1 for j in range(1, 4 + step, step): print(f'''rondje: {i} tel: {j}''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.multi_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_not_turtle() + code=code, expected=expected, extra_check_function=self.is_not_turtle() ) def test_for_loop_arabic(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for دورة in range(١, ٥): - print(دورة)""") + print(دورة)""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ step = 1 if 1 < 5 else -1 for دورة in range(1, 5 + step, step): print(f'''{دورة}''') - time.sleep(0.1)""") + time.sleep(0.1)""" + ) self.single_level_tester( - code=code, - expected=expected, - expected_commands=['for', 'print']) + code=code, expected=expected, expected_commands=["for", "print"] + ) def test_input_with_list(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ color is ['green', 'blue'] - choice is input('Is your favorite color one of: ', color)""") + choice is input('Is your favorite color one of: ', color)""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ color = ['green', 'blue'] choice = input(f'''Is your favorite color one of: {color}''') try: @@ -227,17 +254,17 @@ def test_input_with_list(self): try: choice = float(choice) except ValueError: - pass""") + pass""" + ) self.multi_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_not_turtle() + code=code, expected=expected, extra_check_function=self.is_not_turtle() ) def test_input_without_text_inside(self): code = "x = input()" - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ x = input(f'''''') try: x = int(x) @@ -245,40 +272,41 @@ def test_input_without_text_inside(self): try: x = float(x) except ValueError: - pass""") + pass""" + ) self.multi_level_tester( - code=code, - expected=expected, - extra_check_function=self.is_not_turtle() + code=code, expected=expected, extra_check_function=self.is_not_turtle() ) def test_print_without_text_inside(self): self.multi_level_tester( code="print()", expected="print(f'''''')", - extra_check_function=self.is_not_turtle() + extra_check_function=self.is_not_turtle(), ) # negative tests def test_while_undefined_var(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ while antwoord != 25: - print('hoera')""") + print('hoera')""" + ) self.single_level_tester( - code=code, - exception=hedy.exceptions.UndefinedVarException + code=code, exception=hedy.exceptions.UndefinedVarException ) def test_var_undefined_error_message(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 'Hedy' - print('ik heet ', name)""") + print('ik heet ', name)""" + ) self.multi_level_tester( - code=code, - exception=hedy.exceptions.UndefinedVarException + code=code, exception=hedy.exceptions.UndefinedVarException ) # deze extra check functie kan nu niet mee omdat die altijd op result werkt @@ -287,8 +315,7 @@ def test_var_undefined_error_message(self): def test_input_without_argument(self): self.multi_level_tester( - code="name is input", - exception=hedy.exceptions.IncompleteCommandException + code="name is input", exception=hedy.exceptions.IncompleteCommandException ) # @@ -297,26 +324,25 @@ def test_input_without_argument(self): def test_print_comment(self): code = "print('Hallo welkom bij Hedy!') # This is a comment" expected = "print(f'''Hallo welkom bij Hedy!''')" - output = 'Hallo welkom bij Hedy!' + output = "Hallo welkom bij Hedy!" - self.multi_level_tester( - code=code, - expected=expected, - output=output - ) + self.multi_level_tester(code=code, expected=expected, output=output) # # button tests # def test_if_button_is_pressed_print(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ x = 'PRINT' x is button if PRINT is pressed: - print('The button got pressed!')""") + print('The button got pressed!')""" + ) - expected = HedyTester.dedent(f"""\ + expected = HedyTester.dedent( + f"""\ x = 'PRINT' create_button(x) pygame_end = False @@ -331,15 +357,20 @@ def test_if_button_is_pressed_print(self): if event.key == 'PRINT': print(f'''The button got pressed!''') break - # End of PyGame Event Handler""") + # End of PyGame Event Handler""" + ) self.single_level_tester(code=code, expected=expected) def test_nested_functions(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ def simple_function(): def nested_function(): print(1) - simple_function()""") + simple_function()""" + ) - self.single_level_tester(code=code, exception=hedy.exceptions.NestedFunctionException) + self.single_level_tester( + code=code, exception=hedy.exceptions.NestedFunctionException + ) diff --git a/tests/test_public_programs/extract-programs.py b/tests/test_public_programs/extract-programs.py index 2e6698834bc..172bf1b4b21 100644 --- a/tests/test_public_programs/extract-programs.py +++ b/tests/test_public_programs/extract-programs.py @@ -4,26 +4,26 @@ # The log file itself is not in the repo since it also contains dates & usernames # Felienne will periodically update the filtered file for testing purposes -date = '2023-06-19' +date = "2023-06-19" -with open(f'public-programs-{date}.json', 'r') as public_programs: +with open(f"public-programs-{date}.json", "r") as public_programs: text = public_programs.read() all_programs = json.loads(text) new_programs = [] for program in all_programs: try: - error = program['error']['BOOL'] + error = program["error"]["BOOL"] except Exception: error = True new_program = { - 'code': program['code']['S'], - 'level': program['level']['N'], - 'error': error, - 'language': program['lang']['S'] + "code": program["code"]["S"], + "level": program["level"]["N"], + "error": error, + "language": program["lang"]["S"], } new_programs.append(new_program) -with open(f'filtered-programs-{date}.json', 'w') as public_programs: +with open(f"filtered-programs-{date}.json", "w") as public_programs: save_text = json.dumps(new_programs) public_programs.write(save_text) diff --git a/tests/test_public_programs/test_public_programs.py b/tests/test_public_programs/test_public_programs.py index bd542f94031..aa209c8f04a 100644 --- a/tests/test_public_programs/test_public_programs.py +++ b/tests/test_public_programs/test_public_programs.py @@ -6,25 +6,26 @@ from flask_babel import force_locale import exceptions -most_recent_file_name = 'tests/test_public_programs/filtered-programs-2023-06-19.json' +most_recent_file_name = "tests/test_public_programs/filtered-programs-2023-06-19.json" public_snippets = [] # this file tests all public programs in the database # while saving, they were not broken (no Parse error or other Hedy exception) # these tests make sure we aren't accidentally breaking public programs -with open(most_recent_file_name, 'r') as public_programs_file: +with open(most_recent_file_name, "r") as public_programs_file: text = public_programs_file.read() public_programs = json.loads(text) for p in public_programs: - s = Snippet(filename='file', - level=int(p['level']), - field_name='field', - code=p['code'], - language=p['language'], - error=p['error'] - ) + s = Snippet( + filename="file", + level=int(p["level"]), + field_name="field", + code=p["code"], + language=p["language"], + error=p["error"], + ) public_snippets.append(s) p2 = [(s.name, s) for s in public_snippets] @@ -36,13 +37,18 @@ class TestsPublicPrograms(HedyTester): @parameterized.expand(p2) def test_programs(self, name, snippet): # test correct programs - if snippet is not None and len(snippet.code) > 0 and len(snippet.code) < 100 and not snippet.error: + if ( + snippet is not None + and len(snippet.code) > 0 + and len(snippet.code) < 100 + and not snippet.error + ): try: self.single_level_tester( code=snippet.code, level=int(snippet.level), lang=snippet.language, - translate=False + translate=False, ) # useful code if you want to test what erroneous snippets are now passing @@ -53,7 +59,9 @@ def test_programs(self, name, snippet): # except Exception as e: # pass - except hedy.exceptions.CodePlaceholdersPresentException: # Code with blanks is allowed + except ( + hedy.exceptions.CodePlaceholdersPresentException + ): # Code with blanks is allowed pass except OSError: return None # programs with ask cannot be tested with output :( @@ -61,17 +69,21 @@ def test_programs(self, name, snippet): try: location = E.error_location except BaseException: - location = 'No Location Found' + location = "No Location Found" # Must run this in the context of the Flask app, because FlaskBabel requires that. with app.app_context(): - with force_locale('en'): - error_message = translate_error(E.error_code, E.arguments, 'en') - error_message = error_message.replace('', '`') - error_message = error_message.replace('', '`') - print(f'\n----\n{snippet.code}\n----') - print(f'in language {snippet.language} from level {snippet.level} gives error:') - print(f'{error_message} at line {location}') + with force_locale("en"): + error_message = translate_error(E.error_code, E.arguments, "en") + error_message = error_message.replace( + '', "`" + ) + error_message = error_message.replace("", "`") + print(f"\n----\n{snippet.code}\n----") + print( + f"in language {snippet.language} from level {snippet.level} gives error:" + ) + print(f"{error_message} at line {location}") raise E # test if we are not validating previously incorrect programs @@ -81,5 +93,5 @@ def test_programs(self, name, snippet): level=int(snippet.level), lang=snippet.language, translate=False, - exception=exceptions.HedyException + exception=exceptions.HedyException, ) diff --git a/tests/test_python_prefixes.py b/tests/test_python_prefixes.py index 5c17fc78921..029c42f53fd 100644 --- a/tests/test_python_prefixes.py +++ b/tests/test_python_prefixes.py @@ -3,20 +3,28 @@ from prefixes.normal import convert_numerals convert_numerals_test_data = [ - ('Latin', 1234567890, 1234567890), - ('Latin', '١٢٣٤٥٦٧٨٩٠', 1234567890), - ('Arabic', 1234567890, '١٢٣٤٥٦٧٨٩٠'), - ('Latin', 1234567890.0987654321, 1234567890.0987654321), - ('Latin', '١٢٣٤٥٦٧٨٩٠.٠٩٨٧٦٥٤', 1234567890.0987654321), - ('Arabic', 1234567890.0987654321, '١٢٣٤٥٦٧٨٩٠.٠٩٨٧٦٥٤'), - ('Latin', -1234567890, -1234567890), - ('Latin', '-١٢٣٤٥٦٧٨٩٠', -1234567890), - ('Arabic', -1234567890, '-١٢٣٤٥٦٧٨٩٠'), - ('Latin', -1234567890.0987654321, -1234567890.0987654321), - ('Latin', '-١٢٣٤٥٦٧٨٩٠.٠٩٨٧٦٥٤', -1234567890.0987654321), - ('Arabic', -1234567890.0987654321, '-١٢٣٤٥٦٧٨٩٠.٠٩٨٧٦٥٤'), - ('Latin', '1 Thing, this is 1 arbitrary string', '1 Thing, this is 1 arbitrary string'), - ('Arabic', '1 Thing, this is 1 arbitrary string', '1 Thing, this is 1 arbitrary string'), + ("Latin", 1234567890, 1234567890), + ("Latin", "١٢٣٤٥٦٧٨٩٠", 1234567890), + ("Arabic", 1234567890, "١٢٣٤٥٦٧٨٩٠"), + ("Latin", 1234567890.0987654321, 1234567890.0987654321), + ("Latin", "١٢٣٤٥٦٧٨٩٠.٠٩٨٧٦٥٤", 1234567890.0987654321), + ("Arabic", 1234567890.0987654321, "١٢٣٤٥٦٧٨٩٠.٠٩٨٧٦٥٤"), + ("Latin", -1234567890, -1234567890), + ("Latin", "-١٢٣٤٥٦٧٨٩٠", -1234567890), + ("Arabic", -1234567890, "-١٢٣٤٥٦٧٨٩٠"), + ("Latin", -1234567890.0987654321, -1234567890.0987654321), + ("Latin", "-١٢٣٤٥٦٧٨٩٠.٠٩٨٧٦٥٤", -1234567890.0987654321), + ("Arabic", -1234567890.0987654321, "-١٢٣٤٥٦٧٨٩٠.٠٩٨٧٦٥٤"), + ( + "Latin", + "1 Thing, this is 1 arbitrary string", + "1 Thing, this is 1 arbitrary string", + ), + ( + "Arabic", + "1 Thing, this is 1 arbitrary string", + "1 Thing, this is 1 arbitrary string", + ), ] diff --git a/tests/test_querylog.py b/tests/test_querylog.py index 8d45b3abcb7..7c0f4023dd5 100644 --- a/tests/test_querylog.py +++ b/tests/test_querylog.py @@ -12,22 +12,22 @@ def _fake_transmitter(self, ts, records): self.records.extend(records) def test_regular_xmit(self): - with querylog.LogRecord(banaan='geel') as record: - record.set(bloem='rood') + with querylog.LogRecord(banaan="geel") as record: + record.set(bloem="rood") querylog.LOG_QUEUE.transmit_now() self.assertEqual(len(self.records), 1) - self.assertEqual(self.records[0]['banaan'], 'geel') - self.assertEqual(self.records[0]['bloem'], 'rood') + self.assertEqual(self.records[0]["banaan"], "geel") + self.assertEqual(self.records[0]["bloem"], "rood") def test_emergency_recovery(self): - querylog.begin_global_log_record(banaan='geel') - querylog.log_value(bloem='rood') + querylog.begin_global_log_record(banaan="geel") + querylog.log_value(bloem="rood") querylog.emergency_shutdown() - recovered_queue = log_queue.LogQueue('querylog', batch_window_s=300) + recovered_queue = log_queue.LogQueue("querylog", batch_window_s=300) recovered_queue.try_load_emergency_saves() recovered_queue.set_transmitter(self._fake_transmitter) @@ -35,6 +35,6 @@ def test_emergency_recovery(self): recovered_queue.transmit_now() - self.assertEqual(self.records[0]['banaan'], 'geel') - self.assertEqual(self.records[0]['bloem'], 'rood') - self.assertEqual(self.records[0]['terminated'], True) + self.assertEqual(self.records[0]["banaan"], "geel") + self.assertEqual(self.records[0]["bloem"], "rood") + self.assertEqual(self.records[0]["terminated"], True) diff --git a/tests/test_safe_format.py b/tests/test_safe_format.py index 8681bcd2bcc..b730da9f8ae 100644 --- a/tests/test_safe_format.py +++ b/tests/test_safe_format.py @@ -4,10 +4,10 @@ class TestSafeFormat(unittest.TestCase): def test_normal_operation(self): - self.assertEqual(safe_format('a {b} c', b='b'), 'a b c') + self.assertEqual(safe_format("a {b} c", b="b"), "a b c") def test_missing_key(self): - self.assertEqual(safe_format('a {b} c'), 'a {b} c') + self.assertEqual(safe_format("a {b} c"), "a {b} c") def test_superfluous_key(self): - self.assertEqual(safe_format('a {b} c', bb='bb'), 'a {b} c') + self.assertEqual(safe_format("a {b} c", bb="bb"), "a {b} c") diff --git a/tests/test_snippets/test_adventures.py b/tests/test_snippets/test_adventures.py index a4a1e2ca308..9efe8878772 100644 --- a/tests/test_snippets/test_adventures.py +++ b/tests/test_snippets/test_adventures.py @@ -11,7 +11,7 @@ from website.yaml_file import YamlFile # Set the current directory to the root Hedy folder -os.chdir(os.path.join(os.getcwd(), __file__.replace(os.path.basename(__file__), ''))) +os.chdir(os.path.join(os.getcwd(), __file__.replace(os.path.basename(__file__), ""))) filtered_language = None level = None @@ -19,27 +19,31 @@ def collect_snippets(path, filtered_language=None): Hedy_snippets = [] - files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and f.endswith('.yaml')] + files = [ + f + for f in os.listdir(path) + if os.path.isfile(os.path.join(path, f)) and f.endswith(".yaml") + ] for f in files: lang = f.split(".")[0] if not filtered_language or (filtered_language and lang == filtered_language): f = os.path.join(path, f) yaml = YamlFile.for_file(f) - for name, adventure in yaml['adventures'].items(): + for name, adventure in yaml["adventures"].items(): # the default tab sometimes contains broken code to make a point to learners about changing syntax. - if not name == 'default': - for level_number in adventure['levels']: + if not name == "default": + for level_number in adventure["levels"]: if level_number > hedy.HEDY_MAX_LEVEL: - print('content above max level!') + print("content above max level!") else: - level = adventure['levels'][level_number] - adventure_name = adventure['name'] + level = adventure["levels"][level_number] + adventure_name = adventure["name"] code_snippet_counter = 0 # code snippets inside story_text - for tag in utils.markdown_to_html_tags(level['story_text']): - if tag.name != 'pre' or not tag.contents[0]: + for tag in utils.markdown_to_html_tags(level["story_text"]): + if tag.name != "pre" or not tag.contents[0]: continue # Can be used to catch more languages with example codes in the story_text # feedback = f"Example code in story text {lang}, {adventure_name}, @@ -55,30 +59,38 @@ def collect_snippets(path, filtered_language=None): snippet = Snippet( filename=f, level=level_number, - field_name=adventure_name + ' snippet #' + str(code_snippet_counter), + field_name=adventure_name + + " snippet #" + + str(code_snippet_counter), code=code, - adventure_name=adventure_name) + adventure_name=adventure_name, + ) Hedy_snippets.append(snippet) # code snippets inside start_code try: - start_code = level['start_code'] + start_code = level["start_code"] snippet = Snippet( filename=f, level=level_number, - field_name='start_code', + field_name="start_code", code=start_code, - adventure_name=adventure_name) + adventure_name=adventure_name, + ) Hedy_snippets.append(snippet) except KeyError: - print(f'Problem reading startcode for {lang} level {level}') + print( + f"Problem reading startcode for {lang} level {level}" + ) pass # Code snippets inside example code try: - example_code = utils.markdown_to_html_tags(level['example_code']) + example_code = utils.markdown_to_html_tags( + level["example_code"] + ) for tag in example_code: - if tag.name != 'pre' or not tag.contents[0]: + if tag.name != "pre" or not tag.contents[0]: continue code_snippet_counter += 1 try: @@ -90,21 +102,29 @@ def collect_snippets(path, filtered_language=None): snippet = Snippet( filename=f, level=level_number, - field_name=adventure_name + ' snippet #' + str(code_snippet_counter), + field_name=adventure_name + + " snippet #" + + str(code_snippet_counter), code=code, - adventure_name=adventure_name) + adventure_name=adventure_name, + ) Hedy_snippets.append(snippet) except Exception as E: print(E) return Hedy_snippets + # filtered_language = 'fr' # use this to filter on 1 lang, zh_Hans for Chinese, nb_NO for Norwegian, pt_PT for Portuguese -Hedy_snippets = [(s.name, s) for s in collect_snippets(path='../../content/adventures', - filtered_language=filtered_language)] +Hedy_snippets = [ + (s.name, s) + for s in collect_snippets( + path="../../content/adventures", filtered_language=filtered_language + ) +] # level = 5 # if level: @@ -112,27 +132,27 @@ def collect_snippets(path, filtered_language=None): # This allows filtering out languages locally, but will throw an error # on GitHub Actions (or other CI system) so nobody accidentally commits this. -if os.getenv('CI') and (filtered_language or level): - raise RuntimeError('Whoops, it looks like you left a snippet filter in!') +if os.getenv("CI") and (filtered_language or level): + raise RuntimeError("Whoops, it looks like you left a snippet filter in!") Hedy_snippets = HedyTester.translate_keywords_in_snippets(Hedy_snippets) class TestsAdventurePrograms(HedyTester): - @parameterized.expand(Hedy_snippets, skip_on_empty=True) def test_adventures(self, name, snippet): - if snippet is not None and len(snippet.code) > 0: try: self.single_level_tester( code=snippet.code, level=int(snippet.level), lang=snippet.language, - translate=False + translate=False, ) - except hedy.exceptions.CodePlaceholdersPresentException: # Code with blanks is allowed + except ( + hedy.exceptions.CodePlaceholdersPresentException + ): # Code with blanks is allowed pass except OSError: return None # programs with ask cannot be tested with output :( @@ -140,16 +160,20 @@ def test_adventures(self, name, snippet): try: location = E.error_location except BaseException: - location = 'No Location Found' + location = "No Location Found" # Must run this in the context of the Flask app, because FlaskBabel requires that. with app.app_context(): - with force_locale('en'): - error_message = translate_error(E.error_code, E.arguments, 'en') - error_message = error_message.replace('', '`') - error_message = error_message.replace('', '`') - print(f'\n----\n{snippet.code}\n----') - print(f'from adventure {snippet.adventure_name}') - print(f'in language {snippet.language} from level {snippet.level} gives error:') - print(f'{error_message} at line {location}') + with force_locale("en"): + error_message = translate_error(E.error_code, E.arguments, "en") + error_message = error_message.replace( + '', "`" + ) + error_message = error_message.replace("", "`") + print(f"\n----\n{snippet.code}\n----") + print(f"from adventure {snippet.adventure_name}") + print( + f"in language {snippet.language} from level {snippet.level} gives error:" + ) + print(f"{error_message} at line {location}") raise E diff --git a/tests/test_snippets/test_cheatsheets.py b/tests/test_snippets/test_cheatsheets.py index ee457b77789..b628376da7a 100644 --- a/tests/test_snippets/test_cheatsheets.py +++ b/tests/test_snippets/test_cheatsheets.py @@ -10,12 +10,16 @@ from website.yaml_file import YamlFile # Set the current directory to the root Hedy folder -os.chdir(os.path.join(os.getcwd(), __file__.replace(os.path.basename(__file__), ''))) +os.chdir(os.path.join(os.getcwd(), __file__.replace(os.path.basename(__file__), ""))) def collect_snippets(path): Hedy_snippets = [] - files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and f.endswith('.yaml')] + files = [ + f + for f in os.listdir(path) + if os.path.isfile(os.path.join(path, f)) and f.endswith(".yaml") + ] for file in files: lang = file.split(".")[0] file = os.path.join(path, file) @@ -24,38 +28,44 @@ def collect_snippets(path): for level in yaml: level_number = int(level) if level_number > hedy.HEDY_MAX_LEVEL: - print('content above max level!') + print("content above max level!") else: try: # commands.k.demo_code for k, command in enumerate(yaml[level]): - command_text_short = \ - command['name'] if 'name' in command.keys()\ - else command['explanation'][0:10] + command_text_short = ( + command["name"] + if "name" in command.keys() + else command["explanation"][0:10] + ) snippet = Snippet( filename=file, level=level, - field_name='command ' + - command_text_short + - ' demo_code', - code=command['demo_code']) + field_name="command " + command_text_short + " demo_code", + code=command["demo_code"], + ) Hedy_snippets.append(snippet) except BaseException: - print(f'Problem reading commands yaml for {lang} level {level}') + print(f"Problem reading commands yaml for {lang} level {level}") return Hedy_snippets -Hedy_snippets = [(s.name, s) for s in collect_snippets( - path='../../content/cheatsheets')] +Hedy_snippets = [ + (s.name, s) for s in collect_snippets(path="../../content/cheatsheets") +] Hedy_snippets = HedyTester.translate_keywords_in_snippets(Hedy_snippets) lang = None # lang = 'en' #useful if you want to test just 1 language if lang: - Hedy_snippets = [(name, snippet) for (name, snippet) in Hedy_snippets if snippet.language[:2] == lang] + Hedy_snippets = [ + (name, snippet) + for (name, snippet) in Hedy_snippets + if snippet.language[:2] == lang + ] # # level = 5 # if level: @@ -63,12 +73,11 @@ def collect_snippets(path): # This allows filtering out languages locally, but will throw an error # on GitHub Actions (or other CI system) so nobody accidentally commits this. -if os.getenv('CI') and (lang): - raise RuntimeError('Whoops, it looks like you left a snippet filter in!') +if os.getenv("CI") and (lang): + raise RuntimeError("Whoops, it looks like you left a snippet filter in!") class TestsCheatsheetPrograms(HedyTester): - @parameterized.expand(Hedy_snippets, skip_on_empty=True) def test_cheatsheets_programs(self, name, snippet): if snippet is not None and len(snippet.code) > 0: @@ -77,10 +86,12 @@ def test_cheatsheets_programs(self, name, snippet): code=snippet.code, level=int(snippet.level), lang=snippet.language, - translate=False + translate=False, ) - except hedy.exceptions.CodePlaceholdersPresentException: # Code with blanks is allowed + except ( + hedy.exceptions.CodePlaceholdersPresentException + ): # Code with blanks is allowed pass except OSError: return None # programs with ask cannot be tested with output :( @@ -88,15 +99,19 @@ def test_cheatsheets_programs(self, name, snippet): try: location = E.error_location except BaseException: - location = 'No Location Found' + location = "No Location Found" # Must run this in the context of the Flask app, because FlaskBabel requires that. with app.app_context(): - with force_locale('en'): - error_message = translate_error(E.error_code, E.arguments, 'en') - error_message = error_message.replace('', '`') - error_message = error_message.replace('', '`') - print(f'\n----\n{snippet.code}\n----') - print(f'in language {snippet.language} from level {snippet.level} gives error:') - print(f'{error_message} at line {location}') + with force_locale("en"): + error_message = translate_error(E.error_code, E.arguments, "en") + error_message = error_message.replace( + '', "`" + ) + error_message = error_message.replace("", "`") + print(f"\n----\n{snippet.code}\n----") + print( + f"in language {snippet.language} from level {snippet.level} gives error:" + ) + print(f"{error_message} at line {location}") raise E diff --git a/tests/test_snippets/test_parsons.py b/tests/test_snippets/test_parsons.py index ffb1fab081f..068f9f17753 100644 --- a/tests/test_snippets/test_parsons.py +++ b/tests/test_snippets/test_parsons.py @@ -8,46 +8,49 @@ from website.yaml_file import YamlFile # Set the current directory to the root Hedy folder -os.chdir(os.path.join(os.getcwd(), __file__.replace(os.path.basename(__file__), ''))) +os.chdir(os.path.join(os.getcwd(), __file__.replace(os.path.basename(__file__), ""))) def collect_snippets(path): Hedy_snippets = [] - files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and f.endswith('.yaml')] + files = [ + f + for f in os.listdir(path) + if os.path.isfile(os.path.join(path, f)) and f.endswith(".yaml") + ] for file in files: lang = file.split(".")[0] file = os.path.join(path, file) yaml = YamlFile.for_file(file) - levels = yaml.get('levels') + levels = yaml.get("levels") for level, content in levels.items(): level_number = int(level) if level_number > hedy.HEDY_MAX_LEVEL: - print('content above max level!') + print("content above max level!") else: try: for exercise_id, exercise in levels[level].items(): - code = exercise.get('code') + code = exercise.get("code") snippet = Snippet( filename=file, level=level, field_name=f"{exercise_id}", - code=code) + code=code, + ) Hedy_snippets.append(snippet) except BaseException: - print(f'Problem reading commands yaml for {lang} level {level}') + print(f"Problem reading commands yaml for {lang} level {level}") return Hedy_snippets -Hedy_snippets = [(s.name, s) for s in collect_snippets( - path='../../content/parsons')] +Hedy_snippets = [(s.name, s) for s in collect_snippets(path="../../content/parsons")] Hedy_snippets = HedyTester.translate_keywords_in_snippets(Hedy_snippets) class TestsParsonsPrograms(HedyTester): - @parameterized.expand(Hedy_snippets, skip_on_empty=True) def test_parsons(self, name, snippet): if snippet is not None and len(snippet.code) > 0: @@ -56,10 +59,12 @@ def test_parsons(self, name, snippet): code=snippet.code, level=int(snippet.level), lang=snippet.language, - translate=False + translate=False, ) - except hedy.exceptions.CodePlaceholdersPresentException: # Code with blanks is allowed + except ( + hedy.exceptions.CodePlaceholdersPresentException + ): # Code with blanks is allowed pass except OSError: return None # programs with ask cannot be tested with output :( @@ -67,15 +72,19 @@ def test_parsons(self, name, snippet): try: location = E.error_location except BaseException: - location = 'No Location Found' + location = "No Location Found" # Must run this in the context of the Flask app, because FlaskBabel requires that. with app.app_context(): - with force_locale('en'): - error_message = translate_error(E.error_code, E.arguments, 'en') - error_message = error_message.replace('', '`') - error_message = error_message.replace('', '`') - print(f'\n----\n{snippet.code}\n----') - print(f'in language {snippet.language} from level {snippet.level} gives error:') - print(f'{error_message} at line {location}') + with force_locale("en"): + error_message = translate_error(E.error_code, E.arguments, "en") + error_message = error_message.replace( + '', "`" + ) + error_message = error_message.replace("", "`") + print(f"\n----\n{snippet.code}\n----") + print( + f"in language {snippet.language} from level {snippet.level} gives error:" + ) + print(f"{error_message} at line {location}") raise E diff --git a/tests/test_translation_error.py b/tests/test_translation_error.py index a620fa2dcab..3bd6f4b2be4 100644 --- a/tests/test_translation_error.py +++ b/tests/test_translation_error.py @@ -15,7 +15,8 @@ def exception_language_input(): def custom_name_func(testcase_func, _, param): (ex, lang) = param.args return parameterized.to_safe_name( - f"{testcase_func.__name__}_{ex.__class__.__name__}_to_{lang}_lang") + f"{testcase_func.__name__}_{ex.__class__.__name__}_to_{lang}_lang" + ) def create_exceptions(): @@ -25,13 +26,15 @@ def create_exceptions(): def create_exception(ex_class): ex_args = [ - f'{n}-value' for n in ex_class.__init__.__code__.co_varnames if n not in ['self', 'arguments']] + f"{n}-value" + for n in ex_class.__init__.__code__.co_varnames + if n not in ["self", "arguments"] + ] return ex_class(*ex_args) class TestsTranslationError(HedyTester): - @parameterized.expand(exception_language_input(), name_func=custom_name_func) def test_translate_hedy_exception(self, exception, language): - with app.test_request_context(headers={'Accept-Language': language}): + with app.test_request_context(headers={"Accept-Language": language}): translate_error(exception.error_code, exception.arguments, language) diff --git a/tests/test_translation_level/test_translation_level_01.py b/tests/test_translation_level/test_translation_level_01.py index 945d2ada35e..f991fcacb38 100644 --- a/tests/test_translation_level/test_translation_level_01.py +++ b/tests/test_translation_level/test_translation_level_01.py @@ -2,6 +2,7 @@ import hedy import hedy_translation + # from test_level_01 import HedyTester from tests.Tester import HedyTester @@ -14,21 +15,25 @@ class TestsTranslationLevel1(HedyTester): level = 1 - keywords_from = hedy_translation.keywords_to_dict('en') - keywords_to = hedy_translation.keywords_to_dict('nl') + keywords_from = hedy_translation.keywords_to_dict("en") + keywords_to = hedy_translation.keywords_to_dict("nl") def test_print_english_dutch(self): - code = 'print Hallo welkom bij Hedy!' + code = "print Hallo welkom bij Hedy!" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = 'print Hallo welkom bij Hedy!' + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = "print Hallo welkom bij Hedy!" self.assertEqual(expected, result) def test_ask_english_dutch(self): code = "ask Hallo welkom bij Hedy!" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "vraag Hallo welkom bij Hedy!" self.assertEqual(expected, result) @@ -36,23 +41,29 @@ def test_ask_english_dutch(self): def test_echo_english_dutch(self): code = "ask Hallo welkom bij Hedy!\necho" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "vraag Hallo welkom bij Hedy!\necho" self.assertEqual(expected, result) def test_ask_echo_english_dutch(self): - code = 'print Hallo welkom bij Hedy\'\'\nvraag hoe heet je\necho' + code = "print Hallo welkom bij Hedy''\nvraag hoe heet je\necho" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) - expected = 'print Hallo welkom bij Hedy\'\'\nask hoe heet je\necho' + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) + expected = "print Hallo welkom bij Hedy''\nask hoe heet je\necho" self.assertEqual(expected, result) def test_print_kewords_english_dutch(self): code = "print print ask echo" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "print print ask echo" self.assertEqual(expected, result) @@ -60,7 +71,9 @@ def test_print_kewords_english_dutch(self): def test_forward_english_dutch(self): code = "forward 50" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "vooruit 50" self.assertEqual(expected, result) @@ -68,23 +81,29 @@ def test_forward_english_dutch(self): def test_turn_english_dutch(self): code = "turn left" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "draai links" self.assertEqual(expected, result) def test_print_dutch_english(self): - code = 'print Hallo welkom bij Hedy!' + code = "print Hallo welkom bij Hedy!" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) - expected = 'print Hallo welkom bij Hedy!' + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) + expected = "print Hallo welkom bij Hedy!" self.assertEqual(expected, result) def test_ask_dutch_english(self): code = "vraag Hallo welkom bij Hedy!\nvraag veel plezier" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "ask Hallo welkom bij Hedy!\nask veel plezier" self.assertEqual(expected, result) @@ -92,23 +111,29 @@ def test_ask_dutch_english(self): def test_echo_dutch_english(self): code = "vraag stel je vraag\necho tekst" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "ask stel je vraag\necho tekst" self.assertEqual(expected, result) def test_ask_echo_dutch_english(self): - code = 'vraag Hallo welkom bij Hedy!\necho hoi' + code = "vraag Hallo welkom bij Hedy!\necho hoi" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) - expected = 'ask Hallo welkom bij Hedy!\necho hoi' + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) + expected = "ask Hallo welkom bij Hedy!\necho hoi" self.assertEqual(expected, result) def test_ask_kewords_dutch_english(self): code = "vraag print ask echo" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "ask print ask echo" self.assertEqual(expected, result) @@ -116,7 +141,9 @@ def test_ask_kewords_dutch_english(self): def test_turn_dutch_english(self): code = "draai left" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "turn left" self.assertEqual(expected, result) @@ -124,23 +151,31 @@ def test_turn_dutch_english(self): def test_turn_dutch_english_no_argument(self): code = "draai" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "turn" self.assertEqual(expected, result) def test_translate_back(self): - code = 'print Hallo welkom bij Hedy\nask hoe heet je\necho' + code = "print Hallo welkom bij Hedy\nask hoe heet je\necho" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - result = hedy_translation.translate_keywords(result, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + result = hedy_translation.translate_keywords( + result, from_lang="nl", to_lang="en", level=self.level + ) self.assertEqual(code, result) def test_invalid(self): code = "hallo" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "hallo" self.assertEqual(expected, result) @@ -149,7 +184,9 @@ def test_invalid(self): def test_invalid_space(self): code = " ask Hedy" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = " ask Hedy" self.assertEqual(expected, result) @@ -157,18 +194,20 @@ def test_invalid_space(self): def no_argument_ask(self): code = "ask" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "ask" self.assertEqual(expected, result) - @parameterized.expand([('en', 'forward'), ('es', 'adelante')]) + @parameterized.expand([("en", "forward"), ("es", "adelante")]) def test_forward_type_error_translates_command(self, lang, forward): - code = f'{forward} text' + code = f"{forward} text" self.multi_level_tester( lang=lang, code=code, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(forward) + extra_check_function=self.exception_command(forward), ) diff --git a/tests/test_translation_level/test_translation_level_02.py b/tests/test_translation_level/test_translation_level_02.py index 701f0bb70e8..6d2798b1f85 100644 --- a/tests/test_translation_level/test_translation_level_02.py +++ b/tests/test_translation_level/test_translation_level_02.py @@ -20,8 +20,7 @@ class TestsTranslationLevel2(HedyTester): def test_print(self): code = "print Hallo welkom bij Hedy!" - result = hedy_translation.translate_keywords( - code, "nl", "en", self.level) + result = hedy_translation.translate_keywords(code, "nl", "en", self.level) expected = "print Hallo welkom bij Hedy!" self.assertEqual(expected, result) @@ -29,8 +28,7 @@ def test_print(self): def test_print_kewords(self): code = "print print ask echo" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = "print print ask echo" self.assertEqual(expected, result) @@ -38,8 +36,7 @@ def test_print_kewords(self): def test_ask_assign_english_dutch(self): code = "mens is ask Hallo welkom bij Hedy!" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = "mens is vraag Hallo welkom bij Hedy!" self.assertEqual(expected, result) @@ -47,8 +44,7 @@ def test_ask_assign_english_dutch(self): def test_forward_assigned_value_english_dutch(self): code = "value is 50\nforward value" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = "value is 50\nvooruit value" self.assertEqual(expected, result) @@ -56,8 +52,7 @@ def test_forward_assigned_value_english_dutch(self): def test_sleep(self): code = "sleep" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = "slaap" self.assertEqual(expected, result) @@ -65,8 +60,7 @@ def test_sleep(self): def test_print_var_text(self): code = "welkom is Hallo welkom bij Hedy\nprint welkom Veel plezier" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = "welkom is Hallo welkom bij Hedy\nprint welkom Veel plezier" self.assertEqual(expected, result) @@ -74,8 +68,7 @@ def test_print_var_text(self): def test_ask_kewords(self): code = "hedy is vraag print ask echo" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = "hedy is vraag print ask echo" self.assertEqual(expected, result) @@ -83,8 +76,7 @@ def test_ask_kewords(self): def test_ask_print(self): code = "hedy is hello\nprint hedy" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = "hedy is hello\nprint hedy" self.assertEqual(expected, result) @@ -92,8 +84,7 @@ def test_ask_print(self): def test_ask_assign_dutch_english(self): code = "mens is vraag Hallo welkom bij Hedy!" - result = hedy_translation.translate_keywords( - code, "nl", "en", self.level) + result = hedy_translation.translate_keywords(code, "nl", "en", self.level) expected = "mens is ask Hallo welkom bij Hedy!" self.assertEqual(expected, result) @@ -101,10 +92,8 @@ def test_ask_assign_dutch_english(self): def test_translate_back(self): code = "print welkom bij Hedy\nnaam is ask what is your name\nprint naam" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) - result = hedy_translation.translate_keywords( - code, "nl", "en", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "nl", "en", self.level) expected = "print welkom bij Hedy\nnaam is ask what is your name\nprint naam" @@ -113,8 +102,7 @@ def test_translate_back(self): def test_invalid(self): code = "hallo" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = "hallo" self.assertEqual(expected, result) @@ -122,8 +110,7 @@ def test_invalid(self): def test_invalid_space(self): code = " print Hedy" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = " print Hedy" self.assertEqual(expected, result) @@ -131,8 +118,7 @@ def test_invalid_space(self): def test_echo(self): code = "echo Hedy" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = "echo Hedy" self.assertEqual(expected, result) @@ -142,29 +128,34 @@ def test_echo(self): all_keywords["ask"], all_keywords["is"], all_keywords["print"], - list( - ALL_KEYWORD_LANGUAGES.keys()))) + list(ALL_KEYWORD_LANGUAGES.keys()), + ) + ) def test_ask_print_all_lang(self, ask_keyword, is_keyword, print_keyword, lang): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ {print_keyword} Hello, tell us your name please name {is_keyword} {ask_keyword} Whats your name? - {print_keyword} name is your name!""") + {print_keyword} name is your name!""" + ) result = hedy_translation.translate_keywords( - code, from_lang=lang, to_lang="en", level=self.level) + code, from_lang=lang, to_lang="en", level=self.level + ) - expected = textwrap.dedent(f"""\ + expected = textwrap.dedent( + f"""\ print Hello, tell us your name please name is ask Whats your name? - print name is your name!""") + print name is your name!""" + ) self.assertEqual(expected, result) def no_argument_ask_english(self): code = "ask" - result = hedy_translation.translate_keywords( - code, "en", "nl", self.level) + result = hedy_translation.translate_keywords(code, "en", "nl", self.level) expected = "vraag" self.assertEqual(expected, result) @@ -172,8 +163,7 @@ def no_argument_ask_english(self): def no_argument_ask_dutch(self): code = "vraag" - result = hedy_translation.translate_keywords( - code, "nl", "en", self.level) + result = hedy_translation.translate_keywords(code, "nl", "en", self.level) expected = "ask" self.assertEqual(expected, result) diff --git a/tests/test_translation_level/test_translation_level_03.py b/tests/test_translation_level/test_translation_level_03.py index 07aee027714..34c996ae3eb 100644 --- a/tests/test_translation_level/test_translation_level_03.py +++ b/tests/test_translation_level/test_translation_level_03.py @@ -49,173 +49,202 @@ def test_at_random_nl_en(self): self.assertEqual(expected, result) def test_issue_1856(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print Hoi Ik ben Hedy de Waarzegger vraag is ask Wat wil je weten? print vraag antwoorden is ja, nee, misschien print Mijn glazen bol zegt... sleep 2 - print antwoorden at random""") + print antwoorden at random""" + ) result = hedy_translation.translate_keywords(code, "en", "nl", self.level) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ print Hoi Ik ben Hedy de Waarzegger vraag is vraag Wat wil je weten? print vraag antwoorden is ja, nee, misschien print Mijn glazen bol zegt... slaap 2 - print antwoorden op willekeurig""") + print antwoorden op willekeurig""" + ) self.assertEqual(expected, result) def test_issue_1856_reverse(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print Hoi Ik ben Hedy de Waarzegger vraag is vraag Wat wil je weten? print vraag antwoorden is ja, nee, misschien print Mijn glazen bol zegt... slaap 2 - print antwoorden op willekeurig""") + print antwoorden op willekeurig""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ print Hoi Ik ben Hedy de Waarzegger vraag is ask Wat wil je weten? print vraag antwoorden is ja, nee, misschien print Mijn glazen bol zegt... sleep 2 - print antwoorden at random""") + print antwoorden at random""" + ) result = hedy_translation.translate_keywords(code, "nl", "en", self.level) self.assertEqual(expected, result) def test_issue_1856_v3(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ print Hoi Ik ben Hedy de Waarzegger vraag is ask Wat wil je weten? print vraag antwoorden is ja, nee, misschien print Mijn glazen bol zegt... slaap 2 - print antwoorden op willekeurig""") + print antwoorden op willekeurig""" + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ print Hoi Ik ben Hedy de Waarzegger vraag is ask Wat wil je weten? print vraag antwoorden is ja, nee, misschien print Mijn glazen bol zegt... sleep 2 - print antwoorden at random""") + print antwoorden at random""" + ) result = hedy_translation.translate_keywords(code, "nl", "en", self.level) self.assertEqual(expected, result) def test_add_remove_en_nl(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is koe, kiep add muis to dieren remove koe from dieren - print dieren at random""") + print dieren at random""" + ) result = hedy_translation.translate_keywords(code, "en", "nl", self.level) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ dieren is koe, kiep voeg muis toe aan dieren verwijder koe uit dieren - print dieren op willekeurig""") + print dieren op willekeurig""" + ) self.assertEqual(expected, result) - @parameterized.expand([ - ('en', 'print'), - ('es', 'imprimir'), - ('es', 'print')]) + @parameterized.expand([("en", "print"), ("es", "imprimir"), ("es", "print")]) def test_print_type_error_translates_command(self, lang, command): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ letters is a, b, b - {command} letters""") + {command} letters""" + ) self.multi_level_tester( lang=lang, code=code, max_level=11, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(command) + extra_check_function=self.exception_command(command), ) - @parameterized.expand([ - ('en', 'at'), - ('es', 'en'), - ('es', 'at')]) + @parameterized.expand([("en", "at"), ("es", "en"), ("es", "at")]) def test_list_at_index_type_error_translates_command(self, lang, at): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ letters is ask 'What are the letters?' - print letters {at} 1""") + print letters {at} 1""" + ) self.multi_level_tester( lang=lang, code=code, max_level=11, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(at) + extra_check_function=self.exception_command(at), ) - @parameterized.expand([ - ('en', 'at random'), - ('es', 'en aleatorio'), - ('es', 'at aleatorio'), - ('es', 'en random')]) + @parameterized.expand( + [ + ("en", "at random"), + ("es", "en aleatorio"), + ("es", "at aleatorio"), + ("es", "en random"), + ] + ) def test_list_at_random_type_error_translates_command(self, lang, at_random): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ letters is ask 'What are the letters?' - print letters {at_random}""") + print letters {at_random}""" + ) self.multi_level_tester( lang=lang, code=code, max_level=11, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(at_random) + extra_check_function=self.exception_command(at_random), ) - @parameterized.expand([ - ('en', 'add', 'to'), - ('es', 'añadir', 'a'), - ('es', 'añadir', 'to'), - ('es', 'add', 'a')]) + @parameterized.expand( + [ + ("en", "add", "to"), + ("es", "añadir", "a"), + ("es", "añadir", "to"), + ("es", "add", "a"), + ] + ) def test_add_to_list_type_error_translates_command(self, lang, add, to): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ color is yellow - {add} blue {to} color""") + {add} blue {to} color""" + ) self.multi_level_tester( lang=lang, code=code, max_level=11, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(f'{add} {to}') + extra_check_function=self.exception_command(f"{add} {to}"), ) - @parameterized.expand([ - ('en', 'add', 'to'), - ('es', 'borrar', 'de'), - ('es', 'borrar', 'from'), - ('es', 'remove', 'de')]) + @parameterized.expand( + [ + ("en", "add", "to"), + ("es", "borrar", "de"), + ("es", "borrar", "from"), + ("es", "remove", "de"), + ] + ) def test_remove_from_list_type_error_translates_command(self, lang, remove, from_): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ color is yellow - {remove} blue {from_} color""") + {remove} blue {from_} color""" + ) self.multi_level_tester( lang=lang, code=code, max_level=11, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(f'{remove} {from_}') + extra_check_function=self.exception_command(f"{remove} {from_}"), ) diff --git a/tests/test_translation_level/test_translation_level_04.py b/tests/test_translation_level/test_translation_level_04.py index 112e542edf7..a8bc9c3e531 100644 --- a/tests/test_translation_level/test_translation_level_04.py +++ b/tests/test_translation_level/test_translation_level_04.py @@ -88,16 +88,19 @@ def test_print_assign_nl_en(self): self.assertEqual(expected, result) - @parameterized.expand([('en', 'ask'), ('es', 'preguntar'), ('es', 'ask')]) + @parameterized.expand([("en", "ask"), ("es", "preguntar"), ("es", "ask")]) def test_ask_type_error_translates_command(self, lang, ask): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ colors is orange, blue, green - favorite is {ask} 'Is your fav color' colors""") + favorite is {ask} 'Is your fav color' colors""" + ) self.multi_level_tester( lang=lang, code=code, max_level=11, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=lambda c: c.exception.arguments['line_number'] == 2 and self.exception_command(ask) + extra_check_function=lambda c: c.exception.arguments["line_number"] == 2 + and self.exception_command(ask), ) diff --git a/tests/test_translation_level/test_translation_level_05.py b/tests/test_translation_level/test_translation_level_05.py index d90231140f4..2d8be62efd7 100644 --- a/tests/test_translation_level/test_translation_level_05.py +++ b/tests/test_translation_level/test_translation_level_05.py @@ -16,15 +16,16 @@ class TestsTranslationLevel5(HedyTester): level = 5 - keywords_from = hedy_translation.keywords_to_dict('en') - keywords_to = hedy_translation.keywords_to_dict('nl') + keywords_from = hedy_translation.keywords_to_dict("en") + keywords_to = hedy_translation.keywords_to_dict("nl") all_keywords = hedy_translation.all_keywords_to_dict() def test_print_english_dutch(self): code = "print 'Hallo welkom bij Hedy!'" result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "print 'Hallo welkom bij Hedy!'" self.assertEqual(expected, result) @@ -33,7 +34,8 @@ def test_print2_english_dutch(self): code = "print Hallo 'welkom bij Hedy!'" result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "print Hallo 'welkom bij Hedy!'" self.assertEqual(expected, result) @@ -42,7 +44,8 @@ def test_ask_assign_english_dutch(self): code = "answer is ask 'What is 10 plus 10?'" result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "answer is vraag 'What is 10 plus 10?'" self.assertEqual(expected, result) @@ -51,7 +54,8 @@ def test_if_else_english_dutch(self): code = "if answer is far forward 100 else forward 5" result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "als answer is far vooruit 100 anders vooruit 5" self.assertEqual(expected, result) @@ -60,7 +64,8 @@ def test_in_list_english_dutch(self): code = "if color in pretty_colors print 'pretty!'" result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "als color in pretty_colors print 'pretty!'" self.assertEqual(expected, result) @@ -69,7 +74,8 @@ def test_print_dutch_english(self): code = "print 'Hallo welkom bij Hedy!'" result = hedy_translation.translate_keywords( - code, from_lang="nl", to_lang="en", level=self.level) + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "print 'Hallo welkom bij Hedy!'" self.assertEqual(expected, result) @@ -78,7 +84,8 @@ def test_print2_dutch_english(self): code = "print Hallo 'welkom bij Hedy!'" result = hedy_translation.translate_keywords( - code, from_lang="nl", to_lang="en", level=self.level) + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "print Hallo 'welkom bij Hedy!'" self.assertEqual(expected, result) @@ -87,54 +94,65 @@ def test_ask_assign_dutch_english(self): code = "answer is vraag 'What is 10 plus 10?'" result = hedy_translation.translate_keywords( - code, from_lang="nl", to_lang="en", level=self.level) + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "answer is ask 'What is 10 plus 10?'" self.assertEqual(expected, result) def test_2116(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ people is mom, dad, Emma, Sophie dishwasher is people op willekeurig als dishwasher is Sophie print 'too bad I have to do the dishes' anders - print 'luckily no dishes because' dishwasher 'is already washing up'""") + print 'luckily no dishes because' dishwasher 'is already washing up'""" + ) result = hedy_translation.translate_keywords( - code, from_lang="nl", to_lang="en", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang="nl", to_lang="en", level=self.level + ) + expected = textwrap.dedent( + """\ people is mom, dad, Emma, Sophie dishwasher is people at random if dishwasher is Sophie print 'too bad I have to do the dishes' else - print 'luckily no dishes because' dishwasher 'is already washing up'""") + print 'luckily no dishes because' dishwasher 'is already washing up'""" + ) self.assertEqual(expected, result) def test_if_else_dutch_english(self): - code = textwrap.dedent( - "als answer is far vooruit 100 anders vooruit 5") + code = textwrap.dedent("als answer is far vooruit 100 anders vooruit 5") result = hedy_translation.translate_keywords( - code, from_lang="nl", to_lang="en", level=self.level) + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "if answer is far forward 100 else forward 5" self.assertEqual(expected, result) def test_ifelse_should_go_before_assign(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ kleur is geel als kleur is groen antwoord is ok anders antwoord is stom - print antwoord""") + print antwoord""" + ) result = hedy_translation.translate_keywords( - code, from_lang="nl", to_lang="en", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang="nl", to_lang="en", level=self.level + ) + expected = textwrap.dedent( + """\ kleur is geel if kleur is groen antwoord is ok else antwoord is stom - print antwoord""") + print antwoord""" + ) self.assertEqual(expected, result) @@ -142,7 +160,8 @@ def test_in_list_dutch_english(self): code = "als hond in dieren print 'Cute!'" result = hedy_translation.translate_keywords( - code, from_lang="nl", to_lang="en", level=self.level) + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "if hond in dieren print 'Cute!'" self.assertEqual(expected, result) @@ -153,41 +172,49 @@ def test_in_list_dutch_english(self): all_keywords["print"], all_keywords["is"], all_keywords["else"], - list( - ALL_KEYWORD_LANGUAGES.keys()))) - def test_print_if_is_else_all_lang(self, if_keyword, print_keyword, is_kwrd, else_kwrd, lang): + list(ALL_KEYWORD_LANGUAGES.keys()), + ) + ) + def test_print_if_is_else_all_lang( + self, if_keyword, print_keyword, is_kwrd, else_kwrd, lang + ): code = f"{if_keyword} name {is_kwrd} Hedy {print_keyword} 'Great!' {else_kwrd} {print_keyword} 'Oh no'" result = hedy_translation.translate_keywords( - code, from_lang=lang, to_lang="en", level=self.level) + code, from_lang=lang, to_lang="en", level=self.level + ) expected = "if name is Hedy print 'Great!' else print 'Oh no'" self.assertEqual(expected, result) - @parameterized.expand([('en', 'is'), ('es', 'es'), ('es', 'is')]) + @parameterized.expand([("en", "is"), ("es", "es"), ("es", "is")]) def test_equality_type_error_translates_command(self, lang, is_): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ letters is a, b, c - if letters {is_} '10' print 'wrong!'""") + if letters {is_} '10' print 'wrong!'""" + ) self.multi_level_tester( lang=lang, code=code, max_level=7, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(is_) + extra_check_function=self.exception_command(is_), ) - @parameterized.expand([('en', 'in'), ('es', 'en'), ('es', 'in')]) + @parameterized.expand([("en", "in"), ("es", "en"), ("es", "in")]) def test_in_list_type_error_translates_command(self, lang, in_): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ letters is 'test' - if 10 {in_} letters print 'wrong!'""") + if 10 {in_} letters print 'wrong!'""" + ) self.multi_level_tester( lang=lang, code=code, max_level=7, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(in_) + extra_check_function=self.exception_command(in_), ) diff --git a/tests/test_translation_level/test_translation_level_06.py b/tests/test_translation_level/test_translation_level_06.py index bd6296d8968..986ee330bd9 100644 --- a/tests/test_translation_level/test_translation_level_06.py +++ b/tests/test_translation_level/test_translation_level_06.py @@ -15,13 +15,15 @@ class TestsTranslationLevel6(HedyTester): level = 6 - keywords_from = hedy_translation.keywords_to_dict('en') - keywords_to = hedy_translation.keywords_to_dict('nl') + keywords_from = hedy_translation.keywords_to_dict("en") + keywords_to = hedy_translation.keywords_to_dict("nl") def test_multiplication(self): code = "vermenigvuldiging is 3 * 8" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "vermenigvuldiging is 3 * 8" self.assertEqual(expected, result) @@ -29,7 +31,9 @@ def test_multiplication(self): def test_addition(self): code = "print 'Hallo welkom bij Hedy' 5 + 7" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "print 'Hallo welkom bij Hedy' 5 + 7" self.assertEqual(expected, result) @@ -37,7 +41,9 @@ def test_addition(self): def test_division_dutch_english(self): code = "angle is 360 / angles" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "angle is 360 / angles" self.assertEqual(expected, result) @@ -45,30 +51,42 @@ def test_division_dutch_english(self): def test_division_with_equals_dutch_english(self): code = "angle = 360 / angles" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "angle = 360 / angles" self.assertEqual(expected, result) def test_translate_back_is(self): code = "breuk is 13 / 4" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - result = hedy_translation.translate_keywords(result, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + result = hedy_translation.translate_keywords( + result, from_lang="nl", to_lang="en", level=self.level + ) self.assertEqual(code, result) def test_translate_back_equals(self): code = "breuk = 13 / 4" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - result = hedy_translation.translate_keywords(result, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + result = hedy_translation.translate_keywords( + result, from_lang="nl", to_lang="en", level=self.level + ) self.assertEqual(code, result) def test_ask_with_equals_spanish_english(self): code = "nombre = preguntar '¿Cual es tu nombre?'" - result = hedy_translation.translate_keywords(code, from_lang="es", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="es", to_lang="en", level=self.level + ) expected = "nombre = ask '¿Cual es tu nombre?'" @@ -77,7 +95,9 @@ def test_ask_with_equals_spanish_english(self): def test_ask_with_is_spanish_english(self): code = "nombre es preguntar '¿Cual es tu nombre?'" - result = hedy_translation.translate_keywords(code, from_lang="es", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="es", to_lang="en", level=self.level + ) expected = "nombre is ask '¿Cual es tu nombre?'" @@ -86,50 +106,72 @@ def test_ask_with_is_spanish_english(self): def test_assign_list_is_spanish_english(self): code = "lenguajes es Hedy, Python, C" - result = hedy_translation.translate_keywords(code, from_lang="es", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="es", to_lang="en", level=self.level + ) expected = "lenguajes is Hedy, Python, C" self.assertEqual(result, expected) def test_assign_list_var_spanish_english(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ lenguajes es Hedy, Python, C - a = lenguajes en 0""") + a = lenguajes en 0""" + ) - result = hedy_translation.translate_keywords(code, from_lang="es", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="es", to_lang="en", level=self.level + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ lenguajes is Hedy, Python, C - a = lenguajes at 0""") + a = lenguajes at 0""" + ) self.assertEqual(result, expected) - @parameterized.expand([('en', '='), ('es', '=')]) + @parameterized.expand([("en", "="), ("es", "=")]) def test_equality_type_error_translates_command(self, lang, is_): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ letters is a, b, c - if letters {is_} '10' print 'wrong!'""") + if letters {is_} '10' print 'wrong!'""" + ) self.multi_level_tester( lang=lang, code=code, max_level=7, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(is_) + extra_check_function=self.exception_command(is_), ) - @parameterized.expand([('en', '+'), ('es', '+'), ('en', '-'), ('es', '-'), - ('en', '*'), ('es', '*'), ('en', '/'), ('es', '/')]) + @parameterized.expand( + [ + ("en", "+"), + ("es", "+"), + ("en", "-"), + ("es", "-"), + ("en", "*"), + ("es", "*"), + ("en", "/"), + ("es", "/"), + ] + ) def test_expression_type_error_uses_arith_operator(self, lang, operator): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is test - print a {operator} 2""") + print a {operator} 2""" + ) self.multi_level_tester( lang=lang, code=code, max_level=7, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(operator) + extra_check_function=self.exception_command(operator), ) diff --git a/tests/test_translation_level/test_translation_level_07.py b/tests/test_translation_level/test_translation_level_07.py index f81e8ff7d91..fa1d989435f 100644 --- a/tests/test_translation_level/test_translation_level_07.py +++ b/tests/test_translation_level/test_translation_level_07.py @@ -15,13 +15,15 @@ class TestsTranslationLevel7(HedyTester): level = 7 - keywords_from = hedy_translation.keywords_to_dict('en') - keywords_to = hedy_translation.keywords_to_dict('nl') + keywords_from = hedy_translation.keywords_to_dict("en") + keywords_to = hedy_translation.keywords_to_dict("nl") def test_repeat_english_dutch(self): code = "repeat 3 times print 'Hedy is fun!'" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "herhaal 3 keer print 'Hedy is fun!'" self.assertEqual(expected, result) @@ -29,7 +31,9 @@ def test_repeat_english_dutch(self): def test_repeat2_english_dutch(self): code = "repeat 2 times print name" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "herhaal 2 keer print name" self.assertEqual(expected, result) @@ -37,7 +41,9 @@ def test_repeat2_english_dutch(self): def test_repeat_dutch_english(self): code = "herhaal 3 keer print 'Hedy is fun!'" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "repeat 3 times print 'Hedy is fun!'" self.assertEqual(expected, result) @@ -45,7 +51,9 @@ def test_repeat_dutch_english(self): def test_repeat2_dutch_english(self): code = "herhaal 2 keer print ask" - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) expected = "repeat 2 times print ask" self.assertEqual(expected, result) @@ -53,23 +61,33 @@ def test_repeat2_dutch_english(self): def test_translate_back(self): code = "repeat 4 times print 'Welcome to Hedy'" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - result = hedy_translation.translate_keywords(result, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + result = hedy_translation.translate_keywords( + result, from_lang="nl", to_lang="en", level=self.level + ) self.assertEqual(code, result) - @parameterized.expand([('en', 'repeat', 'times'), - ('es', 'repetir', 'veces'), - ('es', 'repetir', 'times'), - ('es', 'repeat', 'veces')]) + @parameterized.expand( + [ + ("en", "repeat", "times"), + ("es", "repetir", "veces"), + ("es", "repetir", "times"), + ("es", "repeat", "veces"), + ] + ) def test_repeat_type_error_translates_command(self, lang, repeat, times): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ a is 1, 2, 3 - {repeat} a {times} print 'n'""") + {repeat} a {times} print 'n'""" + ) self.single_level_tester( lang=lang, code=code, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(f'{repeat} {times}') + extra_check_function=self.exception_command(f"{repeat} {times}"), ) diff --git a/tests/test_translation_level/test_translation_level_08.py b/tests/test_translation_level/test_translation_level_08.py index ff0479c3ac2..69c2b36df5e 100644 --- a/tests/test_translation_level/test_translation_level_08.py +++ b/tests/test_translation_level/test_translation_level_08.py @@ -15,117 +15,163 @@ class TestsTranslationLevel8(HedyTester): level = 8 - keywords_from = hedy_translation.keywords_to_dict('en') - keywords_to = hedy_translation.keywords_to_dict('nl') + keywords_from = hedy_translation.keywords_to_dict("en") + keywords_to = hedy_translation.keywords_to_dict("nl") def test_repeat_indent_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 3 times - print 'Hedy is fun!'""") + print 'Hedy is fun!'""" + ) - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ herhaal 3 keer - print 'Hedy is fun!'""") + print 'Hedy is fun!'""" + ) self.assertEqual(expected, result) def test_repeat_indent_english_french(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 3 times - print 'Hedy is fun!'""") + print 'Hedy is fun!'""" + ) - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="fr", level=self.level) - expected = textwrap.dedent("""\ + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="fr", level=self.level + ) + expected = textwrap.dedent( + """\ répète 3 fois - affiche 'Hedy is fun!'""") + affiche 'Hedy is fun!'""" + ) self.assertEqual(expected, result) def test_repeat_indent_french_english(self): # todo FH, Nov 2022 in the indenter PR: repete now fails - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ répète 3 fois - affiche 'Hedy is fun!'""") - expected = textwrap.dedent("""\ + affiche 'Hedy is fun!'""" + ) + expected = textwrap.dedent( + """\ repeat 3 times - print 'Hedy is fun!'""") + print 'Hedy is fun!'""" + ) - result = hedy_translation.translate_keywords(code, from_lang="fr", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="fr", to_lang="en", level=self.level + ) self.assertEqual(expected, result) def test_repeat_multiple_indent_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 3 times print 'Hedy is fun!' print 'print 3 keer' - print 'print 1 keer'""") + print 'print 1 keer'""" + ) - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ herhaal 3 keer print 'Hedy is fun!' print 'print 3 keer' - print 'print 1 keer'""") + print 'print 1 keer'""" + ) self.assertEqual(expected, result) def test_repeat_indent_dutch_english(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ herhaal 3 keer - print 'Hedy is fun!'""") + print 'Hedy is fun!'""" + ) - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) - expected = textwrap.dedent("""\ + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) + expected = textwrap.dedent( + """\ repeat 3 times - print 'Hedy is fun!'""") + print 'Hedy is fun!'""" + ) self.assertEqual(expected, result) def test_ifelse_indent_dutch_english(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ if naam is hedy print 'Hallo Hedy' print 'hoe gaat het?' else - print 'Hallo' hedy""") + print 'Hallo' hedy""" + ) - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ als naam is hedy print 'Hallo Hedy' print 'hoe gaat het?' anders - print 'Hallo' hedy""") + print 'Hallo' hedy""" + ) self.assertEqual(expected, result) def test_indent_translate_back(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam = hedy if naam is hedy print 'Hallo Hedy' print 'Hoe gaat het?' repeat 5 times - print '5 keer'""") + print '5 keer'""" + ) - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - result = hedy_translation.translate_keywords(result, from_lang="nl", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + result = hedy_translation.translate_keywords( + result, from_lang="nl", to_lang="en", level=self.level + ) self.assertEqual(code, result) - @parameterized.expand([('es', 'es'), ('es', 'is'), ('en', '='), ('es', '='), ('en', 'is')]) + @parameterized.expand( + [("es", "es"), ("es", "is"), ("en", "="), ("es", "="), ("en", "is")] + ) def test_equality_type_error_translates_command(self, lang, is_): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ letters is a, b, c if letters {is_} '10' - print 'wrong!'""") + print 'wrong!'""" + ) self.multi_level_tester( lang=lang, code=code, max_level=11, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(is_) + extra_check_function=self.exception_command(is_), ) diff --git a/tests/test_translation_level/test_translation_level_09.py b/tests/test_translation_level/test_translation_level_09.py index 76bb0ea792e..26b7ad4cc8a 100644 --- a/tests/test_translation_level/test_translation_level_09.py +++ b/tests/test_translation_level/test_translation_level_09.py @@ -12,63 +12,82 @@ class TestsTranslationLevel9(HedyTester): level = 9 - keywords_from = hedy_translation.keywords_to_dict('en') - keywords_to = hedy_translation.keywords_to_dict('nl') + keywords_from = hedy_translation.keywords_to_dict("en") + keywords_to = hedy_translation.keywords_to_dict("nl") def test_double_repeat_indent_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 3 times repeat 5 times - print 'hi'""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'hi'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ herhaal 3 keer herhaal 5 keer - print 'hi'""") + print 'hi'""" + ) self.assertEqual(expected, result) def test_repeat_ifelse_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ repeat 3 times if naam is hedy print 'hello' else repeat 2 times - print 'oh'""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'oh'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ herhaal 3 keer als naam is hedy print 'hello' anders herhaal 2 keer - print 'oh'""") + print 'oh'""" + ) self.assertEqual(expected, result) def test_multiple_ifelse_dutch_english(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ als 10 is 10 als 2 is 3 print 'wat raar' anders - print 'gelukkig'""") - - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) - expected = textwrap.dedent("""\ + print 'gelukkig'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) + expected = textwrap.dedent( + """\ if 10 is 10 if 2 is 3 print 'wat raar' else - print 'gelukkig'""") + print 'gelukkig'""" + ) self.assertEqual(expected, result) def test_indent_translate_back(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam = hedy if naam is hedy repeat 4 times @@ -76,9 +95,14 @@ def test_indent_translate_back(self): print 'Hoe gaat het?' else repeat 5 times - print '5 keer'""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - result = hedy_translation.translate_keywords(result, from_lang="nl", to_lang="en", level=self.level) + print '5 keer'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + result = hedy_translation.translate_keywords( + result, from_lang="nl", to_lang="en", level=self.level + ) self.assertEqual(code, result) diff --git a/tests/test_translation_level/test_translation_level_10.py b/tests/test_translation_level/test_translation_level_10.py index e678dcc0f00..649676cafa8 100644 --- a/tests/test_translation_level/test_translation_level_10.py +++ b/tests/test_translation_level/test_translation_level_10.py @@ -17,60 +17,85 @@ class TestsTranslationLevel10(HedyTester): level = 10 def test_for_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is hond, kat, vis for dier in dieren - print 'hallo' dier""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'hallo' dier""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ dieren is hond, kat, vis voor dier in dieren - print 'hallo' dier""") + print 'hallo' dier""" + ) self.assertEqual(expected, result) def test_for_dutch_english(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is hond, kat, vis voor dier in dieren voor animal in dieren - print 'hallo' dier""") - - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) - expected = textwrap.dedent("""\ + print 'hallo' dier""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) + expected = textwrap.dedent( + """\ dieren is hond, kat, vis for dier in dieren for animal in dieren - print 'hallo' dier""") + print 'hallo' dier""" + ) self.assertEqual(expected, result) def test_repeat_translate_back(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ dieren is hond, kat, vis voor dier in dieren voor animal in dieren - print 'hallo' dier""") + print 'hallo' dier""" + ) - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) - result = hedy_translation.translate_keywords(result, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) + result = hedy_translation.translate_keywords( + result, from_lang="en", to_lang="nl", level=self.level + ) self.assertEqual(result, code) - @parameterized.expand([('en', 'for', 'in'), - ('es', 'para', 'en'), - ('es', 'para', 'in'), - ('es', 'for', 'en')]) + @parameterized.expand( + [ + ("en", "for", "in"), + ("es", "para", "en"), + ("es", "para", "in"), + ("es", "for", "en"), + ] + ) def test_for_list_type_error_translates_command(self, lang, for_, in_): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ dieren is 'text' {for_} dier {in_} dieren - print dier""") + print dier""" + ) self.multi_level_tester( lang=lang, code=code, max_level=11, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(f'{for_} {in_}')) + extra_check_function=self.exception_command(f"{for_} {in_}"), + ) diff --git a/tests/test_translation_level/test_translation_level_11.py b/tests/test_translation_level/test_translation_level_11.py index cc8dd02ef4a..5e8f8c9264a 100644 --- a/tests/test_translation_level/test_translation_level_11.py +++ b/tests/test_translation_level/test_translation_level_11.py @@ -16,20 +16,25 @@ class TestsTranslationLevel11(HedyTester): level = 11 - keywords_from = hedy_translation.keywords_to_dict('en') - keywords_to = hedy_translation.keywords_to_dict('nl') + keywords_from = hedy_translation.keywords_to_dict("en") + keywords_to = hedy_translation.keywords_to_dict("nl") all_keywords = hedy_translation.all_keywords_to_dict() def test_for_in_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for counter in range 1 to 5 - print counter""") + print counter""" + ) result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ voor counter in bereik 1 tot 5 - print counter""") + print counter""" + ) self.assertEqual(expected, result) @@ -41,45 +46,60 @@ def test_for_in_english_dutch(self): all_keywords["range"], all_keywords["to"], all_keywords["print"], - list(ALL_KEYWORD_LANGUAGES.keys()))) + list(ALL_KEYWORD_LANGUAGES.keys()), + ) + ) def test_for_in_all_lang( - self, - ask_keyword, - for_keyword, - in_keyword, - range_keyword, - to_keyword, - print_keyword, - lang): - code = textwrap.dedent(f"""\ + self, + ask_keyword, + for_keyword, + in_keyword, + range_keyword, + to_keyword, + print_keyword, + lang, + ): + code = textwrap.dedent( + f"""\ nummer = {ask_keyword} 'hoe oud ben je' {for_keyword} counter {in_keyword} {range_keyword} 1 {to_keyword} 5 {for_keyword} count {in_keyword} {range_keyword} nummer {to_keyword} 0 - {print_keyword} 'hoi' counter""") + {print_keyword} 'hoi' counter""" + ) result = hedy_translation.translate_keywords( - code, from_lang=lang, to_lang="en", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang=lang, to_lang="en", level=self.level + ) + expected = textwrap.dedent( + """\ nummer = ask 'hoe oud ben je' for counter in range 1 to 5 for count in range nummer to 0 - print 'hoi' counter""") + print 'hoi' counter""" + ) self.assertEqual(expected, result) - @parameterized.expand([('en', 'in', 'range', 'to'), - ('es', 'en', 'rango', 'a'), - ('es', 'in', 'rango', 'a'), - ('es', 'en', 'rango', 'a')]) + @parameterized.expand( + [ + ("en", "in", "range", "to"), + ("es", "en", "rango", "a"), + ("es", "in", "rango", "a"), + ("es", "en", "rango", "a"), + ] + ) def test_for_loop_type_error_translates_command(self, lang, in_, range_, to): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ end is 'text' for a {in_} {range_} 1 {to} end - print end""") + print end""" + ) self.multi_level_tester( lang=lang, code=code, max_level=11, exception=hedy.exceptions.InvalidArgumentTypeException, - extra_check_function=self.exception_command(f'{in_} {range_} {to}')) + extra_check_function=self.exception_command(f"{in_} {range_} {to}"), + ) diff --git a/tests/test_translation_level/test_translation_level_12.py b/tests/test_translation_level/test_translation_level_12.py index c9ff0511273..b31680bd701 100644 --- a/tests/test_translation_level/test_translation_level_12.py +++ b/tests/test_translation_level/test_translation_level_12.py @@ -16,7 +16,9 @@ class TestsTranslationLevel12(HedyTester): def test_decimal_english_dutch(self): code = "print 2.5 + 2.5" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "print 2.5 + 2.5" self.assertEqual(expected, result) @@ -24,21 +26,29 @@ def test_decimal_english_dutch(self): def test_text_in_quotes_english_dutch(self): code = "naam = 'hedy'" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "naam = 'hedy'" self.assertEqual(expected, result) def test_text_in_quotes_ifs_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam = 'hedy' if naam is 'hedy' - print 'hallo ' naam""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'hallo ' naam""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ naam = 'hedy' als naam is 'hedy' - print 'hallo ' naam""") + print 'hallo ' naam""" + ) self.assertEqual(expected, result) diff --git a/tests/test_translation_level/test_translation_level_13.py b/tests/test_translation_level/test_translation_level_13.py index 53d7537b9eb..731c25f03fb 100644 --- a/tests/test_translation_level/test_translation_level_13.py +++ b/tests/test_translation_level/test_translation_level_13.py @@ -14,61 +14,85 @@ class TestsTranslationLevel13(HedyTester): level = 13 def test_and_condition_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam = 'hedy' leeftijd = 2 if naam is 'hedy' and leeftijd is 2 - print 'hallo'""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'hallo'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ naam = 'hedy' leeftijd = 2 als naam is 'hedy' en leeftijd is 2 - print 'hallo'""") + print 'hallo'""" + ) self.assertEqual(expected, result) def test_or_condition_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam = 'hedy' leeftijd = 2 if naam is 'niet hedy' or leeftijd is 2 - print 'hallo'""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'hallo'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ naam = 'hedy' leeftijd = 2 als naam is 'niet hedy' of leeftijd is 2 - print 'hallo'""") + print 'hallo'""" + ) self.assertEqual(expected, result) def test_and_condition_acces_list_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 'hedy', 'niet hedy' if 'hedy' in naam and 'niet hedy' in naam - print 'hallo'""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'hallo'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ naam is 'hedy', 'niet hedy' als 'hedy' in naam en 'niet hedy' in naam - print 'hallo'""") + print 'hallo'""" + ) self.assertEqual(expected, result) def test_or_condition_acces_list_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ naam is 'hedy', 'niet hedy' if 'hedy' in naam or 'niet hedy' in naam - print 'hallo'""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'hallo'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ naam is 'hedy', 'niet hedy' als 'hedy' in naam of 'niet hedy' in naam - print 'hallo'""") + print 'hallo'""" + ) self.assertEqual(expected, result) diff --git a/tests/test_translation_level/test_translation_level_14.py b/tests/test_translation_level/test_translation_level_14.py index ab57ea9b557..168136036cb 100644 --- a/tests/test_translation_level/test_translation_level_14.py +++ b/tests/test_translation_level/test_translation_level_14.py @@ -18,92 +18,122 @@ class TestsTranslationLevel14(HedyTester): all_keywords_dict = hedy_translation.all_keywords_to_dict() def test_bigger(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ hedy = 5 if hedy > 6 - print 'hedy'""") + print 'hedy'""" + ) result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = 5 als hedy > 6 - print 'hedy'""") + print 'hedy'""" + ) self.assertEqual(expected, result) def test_smaller(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ hedy = 5 if hedy < 6 - print 'hedy'""") + print 'hedy'""" + ) result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = 5 als hedy < 6 - print 'hedy'""") + print 'hedy'""" + ) self.assertEqual(expected, result) def test_bigger_equal(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ hedy = 5 if hedy >= 6 - print 'hedy'""") + print 'hedy'""" + ) result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = 5 als hedy >= 6 - print 'hedy'""") + print 'hedy'""" + ) self.assertEqual(expected, result) def test_smaller_equal(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ hedy = 5 if hedy <= 6 - print 'hedy'""") + print 'hedy'""" + ) result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = 5 als hedy <= 6 - print 'hedy'""") + print 'hedy'""" + ) self.assertEqual(expected, result) def test_not_equal(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ hedy = 5 if hedy != 6 - print 'hedy'""") + print 'hedy'""" + ) result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = 5 als hedy != 6 - print 'hedy'""") + print 'hedy'""" + ) self.assertEqual(expected, result) def test_double_equals(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ hedy = 5 if hedy == 6 - print 'hedy'""") + print 'hedy'""" + ) result = hedy_translation.translate_keywords( - code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = 5 als hedy == 6 - print 'hedy'""") + print 'hedy'""" + ) self.assertEqual(expected, result) @@ -111,18 +141,25 @@ def test_double_equals(self): HedyTester.as_list_of_tuples( all_keywords_dict["if"], all_keywords_dict["print"], - list(ALL_KEYWORD_LANGUAGES.keys()))) + list(ALL_KEYWORD_LANGUAGES.keys()), + ) + ) def test_double_equals_all_lang(self, if_keyword, print_keyword, lang): - code = textwrap.dedent(f"""\ + code = textwrap.dedent( + f"""\ hedy = 5 {if_keyword} hedy == 6 - {print_keyword} 'hedy'""") + {print_keyword} 'hedy'""" + ) result = hedy_translation.translate_keywords( - code, from_lang=lang, to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + code, from_lang=lang, to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = 5 als hedy == 6 - print 'hedy'""") + print 'hedy'""" + ) self.assertEqual(expected, result) diff --git a/tests/test_translation_level/test_translation_level_15.py b/tests/test_translation_level/test_translation_level_15.py index dd715b39bbf..fc48765f906 100644 --- a/tests/test_translation_level/test_translation_level_15.py +++ b/tests/test_translation_level/test_translation_level_15.py @@ -14,51 +14,69 @@ class TestsTranslationLevel15(HedyTester): level = 15 def test_while_loop_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ answer = 0 while answer != 25 answer = ask 'What is 5 * 5' - print 'Good job!'""") + print 'Good job!'""" + ) - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ answer = 0 zolang answer != 25 answer = vraag 'What is 5 * 5' - print 'Good job!'""") + print 'Good job!'""" + ) self.assertEqual(expected, result) def test_while_loop_dutch_english(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ answer = 0 zolang answer != 25 answer = vraag 'What is 5 * 5' - print 'Good job!'""") + print 'Good job!'""" + ) - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) - expected = textwrap.dedent("""\ + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) + expected = textwrap.dedent( + """\ answer = 0 while answer != 25 answer = ask 'What is 5 * 5' - print 'Good job!'""") + print 'Good job!'""" + ) self.assertEqual(expected, result) def test_multiple_while_loop_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ answer = 0 while answer != 25 while answer > 30 answer = ask 'What is 5 * 5' - print 'Good job!'""") + print 'Good job!'""" + ) - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ answer = 0 zolang answer != 25 zolang answer > 30 answer = vraag 'What is 5 * 5' - print 'Good job!'""") + print 'Good job!'""" + ) self.assertEqual(expected, result) diff --git a/tests/test_translation_level/test_translation_level_16.py b/tests/test_translation_level/test_translation_level_16.py index f9f5e5b82ee..537fa7508b0 100644 --- a/tests/test_translation_level/test_translation_level_16.py +++ b/tests/test_translation_level/test_translation_level_16.py @@ -15,31 +15,45 @@ class TestsTranslationLevel16(HedyTester): def test_assign_list_english_dutch(self): code = "fruit = ['appel', 'banaan', 'kers']" - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) expected = "fruit = ['appel', 'banaan', 'kers']" self.assertEqual(expected, result) def test_access_list_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ fruit = ['appel', 'banaan', 'kers'] - print fruit[2]""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print fruit[2]""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ fruit = ['appel', 'banaan', 'kers'] - print fruit[2]""") + print fruit[2]""" + ) self.assertEqual(expected, result) def test_access_list_random_dutch_english(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ fruit = ['appel', 'banaan', 'kers'] - print fruit[willekeurig]""") - - result = hedy_translation.translate_keywords(code, from_lang="nl", to_lang="en", level=self.level) - expected = textwrap.dedent("""\ + print fruit[willekeurig]""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="nl", to_lang="en", level=self.level + ) + expected = textwrap.dedent( + """\ fruit = ['appel', 'banaan', 'kers'] - print fruit[random]""") + print fruit[random]""" + ) self.assertEqual(expected, result) diff --git a/tests/test_translation_level/test_translation_level_17.py b/tests/test_translation_level/test_translation_level_17.py index cb7a619793c..9de9a51e6f1 100644 --- a/tests/test_translation_level/test_translation_level_17.py +++ b/tests/test_translation_level/test_translation_level_17.py @@ -14,95 +14,131 @@ class TestsTranslationLevel17(HedyTester): level = 17 def test_indent_for_loop_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range 1 to 12: - print 'Hedy' i""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'Hedy' i""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ voor i in bereik 1 tot 12: - print 'Hedy' i""") + print 'Hedy' i""" + ) self.assertEqual(expected, result) def test_indent_while_loop_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ i = 3 while i < 2: - print 'Hedy' i""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'Hedy' i""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ i = 3 zolang i < 2: - print 'Hedy' i""") + print 'Hedy' i""" + ) self.assertEqual(expected, result) def test_indent_for_list_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ hedy = ['hedy', 'andre', 'luca'] for naam in hedy: - print naam""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print naam""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = ['hedy', 'andre', 'luca'] voor naam in hedy: - print naam""") + print naam""" + ) self.assertEqual(expected, result) def test_indent_ifs_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ hedy = 4 if hedy is 4: - print 'hedy'""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'hedy'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = 4 als hedy is 4: - print 'hedy'""") + print 'hedy'""" + ) self.assertEqual(expected, result) def test_indent_elses_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ hedy = 4 if hedy is 4: print 'hedy' else: - print 'nee'""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'nee'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = 4 als hedy is 4: print 'hedy' anders: - print 'nee'""") + print 'nee'""" + ) self.assertEqual(expected, result) def test_elif_english_dutch(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ hedy = 4 if hedy is 4: print 'hedy' elif hedy is 5: print 5 else: - print 'nee'""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print 'nee'""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ hedy = 4 als hedy is 4: print 'hedy' alsanders hedy is 5: print 5 anders: - print 'nee'""") + print 'nee'""" + ) self.assertEqual(expected, result) diff --git a/tests/test_translation_level/test_translation_level_18.py b/tests/test_translation_level/test_translation_level_18.py index c118efc17a2..117ceb9d59a 100644 --- a/tests/test_translation_level/test_translation_level_18.py +++ b/tests/test_translation_level/test_translation_level_18.py @@ -13,46 +13,66 @@ class TestsTranslationLevel18(HedyTester): level = 18 def test_input(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ leeftijd is input('Hoe oud ben jij?') - print(leeftijd)""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print(leeftijd)""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ leeftijd is invoer('Hoe oud ben jij?') - print(leeftijd)""") + print(leeftijd)""" + ) self.assertEqual(expected, result) def test_range_with_brackets(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ for i in range(0, 10): - print('hallo!')""") - - result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="nl", level=self.level) - expected = textwrap.dedent("""\ + print('hallo!')""" + ) + + result = hedy_translation.translate_keywords( + code, from_lang="en", to_lang="nl", level=self.level + ) + expected = textwrap.dedent( + """\ voor i in bereik(0, 10): - print('hallo!')""") + print('hallo!')""" + ) self.assertEqual(expected, result) def test_input_empty_brackets(self): - code = textwrap.dedent("""\ + code = textwrap.dedent( + """\ nombre es entrada() - imprimir(nombre)""") + imprimir(nombre)""" + ) - result = hedy_translation.translate_keywords(code, from_lang="es", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="es", to_lang="en", level=self.level + ) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ nombre is input() - print(nombre)""") + print(nombre)""" + ) self.assertEqual(expected, result) def test_print_empty_brackets(self): code = textwrap.dedent("imprimir()") - result = hedy_translation.translate_keywords(code, from_lang="es", to_lang="en", level=self.level) + result = hedy_translation.translate_keywords( + code, from_lang="es", to_lang="en", level=self.level + ) expected = textwrap.dedent("print()") diff --git a/tests/test_utils.py b/tests/test_utils.py index e7c59f55e21..1e36b3a3e16 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -8,19 +8,16 @@ class TestUtils(unittest.TestCase): def test_slashjoin(self): self.assertEqual( - 'http://hedycode.com/banaan/xyz', - utils.slash_join( - 'http://hedycode.com/', - '/banaan', - '', - '/xyz')) - self.assertEqual('/one/two', utils.slash_join('/one/two')) - self.assertEqual('/one/two', utils.slash_join(None, '/one/two')) + "http://hedycode.com/banaan/xyz", + utils.slash_join("http://hedycode.com/", "/banaan", "", "/xyz"), + ) + self.assertEqual("/one/two", utils.slash_join("/one/two")) + self.assertEqual("/one/two", utils.slash_join(None, "/one/two")) def test_extract_rounds(self): - salt = bcrypt.gensalt(rounds=7).decode('utf-8') + salt = bcrypt.gensalt(rounds=7).decode("utf-8") self.assertEqual(7, utils.extract_bcrypt_rounds(salt)) def test_extract_default_rounds(self): - salt = bcrypt.gensalt().decode('utf-8') + salt = bcrypt.gensalt().decode("utf-8") self.assertEqual(12, utils.extract_bcrypt_rounds(salt)) diff --git a/tests/tests_z_yamlfile.py b/tests/tests_z_yamlfile.py index f280bff2403..44f96112725 100644 --- a/tests/tests_z_yamlfile.py +++ b/tests/tests_z_yamlfile.py @@ -16,7 +16,7 @@ def test_load_yaml_equivalent(self): # Pick a file with unicode in it so we're sure it gets handled properly root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) - yaml_file = os.path.normpath(root_dir + '/content/adventures/hu.yaml') + yaml_file = os.path.normpath(root_dir + "/content/adventures/hu.yaml") file = YamlFile(yaml_file) # Remove pickled version of this file if it exists, it may @@ -39,5 +39,6 @@ def test_load_yaml_equivalent(self): self.assertEqual(original_data, cached_data) print( - f'YAML loading takes {original_seconds / n} seconds, unpickling takes {cached_seconds / n}' - f'({original_seconds / cached_seconds:.1f}x faster)') + f"YAML loading takes {original_seconds / n} seconds, unpickling takes {cached_seconds / n}" + f"({original_seconds / cached_seconds:.1f}x faster)" + ) diff --git a/tests_e2e.py b/tests_e2e.py index 14aacfff625..c19f86ed29d 100644 --- a/tests_e2e.py +++ b/tests_e2e.py @@ -19,9 +19,9 @@ from hedy import HEDY_MAX_LEVEL from hedy_content import ALL_LANGUAGES -HOST = os.getenv('ENDPOINT', 'http://localhost:' + str(CONFIG['port']) + '/') -if not HOST.endswith('/'): - HOST += '/' +HOST = os.getenv("ENDPOINT", "http://localhost:" + str(CONFIG["port"]) + "/") +if not HOST.endswith("/"): + HOST += "/" # This dict has global scope and holds all created users and their still # current sessions (as cookies), for convenient reuse wherever needed @@ -30,47 +30,51 @@ # *** HELPERS *** -def request(method, path, headers={}, body='', cookies=None): - - if method not in ['get', 'post', 'put', 'delete']: - raise Exception('request - Invalid method: ' + str(method)) +def request(method, path, headers={}, body="", cookies=None): + if method not in ["get", "post", "put", "delete"]: + raise Exception("request - Invalid method: " + str(method)) # We pass the X-Testing header to let the server know that this is a # request coming from an E2E test, thus no transactional emails should be # sent. - headers['X-Testing'] = '1' + headers["X-Testing"] = "1" # If sending an object as body, stringify it and set the proper content-type header if isinstance(body, dict): - headers['content-type'] = 'application/json' + headers["content-type"] = "application/json" body = json.dumps(body) start = utils.timems() - response = getattr(requests, method)(HOST + path, headers=headers, data=body, cookies=cookies) + response = getattr(requests, method)( + HOST + path, headers=headers, data=body, cookies=cookies + ) # Remember all cookies in the cookie jar if cookies is not None: cookies.update(response.cookies) - ret = {'time': utils.timems() - start} + ret = {"time": utils.timems() - start} if response.history and response.history[0]: # This code branch will be executed if there is a redirect - ret['code'] = response.history[0].status_code - ret['headers'] = response.history[0].headers - if getattr(response.history[0], '_content'): + ret["code"] = response.history[0].status_code + ret["headers"] = response.history[0].headers + if getattr(response.history[0], "_content"): # We can assume that bodies returned from redirected responses are always # plain text, since no JSON endpoint in the server is reachable through a # redirect. - ret['body'] = getattr(response.history[0], '_content').decode('utf-8') + ret["body"] = getattr(response.history[0], "_content").decode("utf-8") else: - ret['code'] = response.status_code - ret['headers'] = response.headers - if 'Content-Type' in response.headers and response.headers['Content-Type'] == 'application/json': - ret['body'] = response.json() + ret["code"] = response.status_code + ret["headers"] = response.headers + if ( + "Content-Type" in response.headers + and response.headers["Content-Type"] == "application/json" + ): + ret["body"] = response.json() else: - ret['body'] = response.text + ret["body"] = response.text return ret @@ -88,41 +92,46 @@ def setUp(self): @property def username(self): - return self.user['username'] if self.user else None + return self.user["username"] if self.user else None def make_username(self): # We create usernames with a random component so that if a test fails, we don't have to do a cleaning # of the DB so that the test suite can run again # This also allows us to run concurrent tests without having username conflicts. - username = 'user' + str(random.randint(10000, 100000)) + username = "user" + str(random.randint(10000, 100000)) return username # If user with `username` exists, return it. Otherwise, create it. def assert_user_exists(self, username): if not isinstance(username, str): - raise Exception('AuthHelper.assert_user_exists - Invalid username: ' + str(username)) + raise Exception( + "AuthHelper.assert_user_exists - Invalid username: " + str(username) + ) if username in USERS: return USERS[username] body = { - 'username': username, - 'email': username + '@hedy.com', - 'language': 'nl', - 'keyword_language': 'en', - 'agree_terms': 'yes', - 'password': 'foobar', - 'password_repeat': 'foobar'} - response = request('post', 'auth/signup', {}, body, cookies=self.user_cookies[username]) + "username": username, + "email": username + "@hedy.com", + "language": "nl", + "keyword_language": "en", + "agree_terms": "yes", + "password": "foobar", + "password_repeat": "foobar", + } + response = request( + "post", "auth/signup", {}, body, cookies=self.user_cookies[username] + ) # It might sometimes happen that by the time we attempted to create the user, another test did it already. # In this case, we get a 403. We invoke the function recursively. - if response['code'] == 403: + if response["code"] == 403: return self.assert_user_exists(username) # Store the user & also the verify token for use in upcoming tests USERS[username] = body - USERS[username]['verify_token'] = response['body']['token'] + USERS[username]["verify_token"] = response["body"]["token"] return USERS[username] # Returns the first created user, if any; otherwise, creates one. @@ -134,18 +143,19 @@ def get_any_user(self): def get_any_logged_user(self): # If there's no logged in user, we login the user user = self.get_any_user() - return self.login_user(user['username']) + return self.login_user(user["username"]) def login_user(self, username): """Login another user. Does not BECOME that user for all subsequent request.""" self.assert_user_exists(username) user = USERS[username] - request('post', - 'auth/login', - {}, - {'username': user['username'], - 'password': user['password']}, - cookies=self.user_cookies[username]) + request( + "post", + "auth/login", + {}, + {"username": user["username"], "password": user["password"]}, + cookies=self.user_cookies[username], + ) return user def given_specific_user_is_logged_in(self, username): @@ -157,7 +167,7 @@ def get_hedy_cookie(self, cookie_string): cookie.load(cookie_string) for key, cookie in cookie.items(): - if key == CONFIG['session']['cookie_name']: + if key == CONFIG["session"]["cookie_name"]: return cookie def given_fresh_user_is_logged_in(self): @@ -169,7 +179,9 @@ def make_current_user_teacher(self): Need to log in again to refresh the session. """ - self.post_data('admin/markAsTeacher', {'username': self.username, 'is_teacher': True}) + self.post_data( + "admin/markAsTeacher", {"username": self.username, "is_teacher": True} + ) return self.login_user(self.username) def given_user_is_logged_in(self): @@ -187,54 +199,77 @@ def given_any_user(self): self.user = self.get_any_user() def switch_user(self, user): - self.user = self.login_user(user['username']) + self.user = self.login_user(user["username"]) def post_data( - self, - path, - body, - expect_http_code=200, - no_cookie=False, - return_headers=False, - put_data=False): - cookies = self.user_cookies[self.username] if self.username and not no_cookie else None - - method = 'put' if put_data else 'post' + self, + path, + body, + expect_http_code=200, + no_cookie=False, + return_headers=False, + put_data=False, + ): + cookies = ( + self.user_cookies[self.username] + if self.username and not no_cookie + else None + ) + + method = "put" if put_data else "post" response = request(method, path, body=body, cookies=cookies) self.assertEqual( - response['code'], + response["code"], expect_http_code, - f'While {method}ing {body} to {path} (user: {self.username}). Response: {response["body"]}') + f'While {method}ing {body} to {path} (user: {self.username}). Response: {response["body"]}', + ) - return response['headers'] if return_headers else response['body'] + return response["headers"] if return_headers else response["body"] - def delete_data(self, path, expect_http_code=200, no_cookie=False, return_headers=False): - cookies = self.user_cookies[self.username] if self.username and not no_cookie else None + def delete_data( + self, path, expect_http_code=200, no_cookie=False, return_headers=False + ): + cookies = ( + self.user_cookies[self.username] + if self.username and not no_cookie + else None + ) - method = 'delete' + method = "delete" response = request(method, path, cookies=cookies) - self.assertEqual(response['code'], expect_http_code, - f'While {method}ing {path} (user: {self.username})') + self.assertEqual( + response["code"], + expect_http_code, + f"While {method}ing {path} (user: {self.username})", + ) - return response['headers'] if return_headers else response['body'] + return response["headers"] if return_headers else response["body"] - def get_data(self, path, expect_http_code=200, no_cookie=False, return_headers=False): - cookies = self.user_cookies[self.username] if self.username and not no_cookie else None - response = request('get', path, body='', cookies=cookies) + def get_data( + self, path, expect_http_code=200, no_cookie=False, return_headers=False + ): + cookies = ( + self.user_cookies[self.username] + if self.username and not no_cookie + else None + ) + response = request("get", path, body="", cookies=cookies) self.assertEqual( - response['code'], + response["code"], expect_http_code, - f'While reading {path} (user: {self.username}). Response: {response["body"]}') + f'While reading {path} (user: {self.username}). Response: {response["body"]}', + ) - return response['headers'] if return_headers else response['body'] + return response["headers"] if return_headers else response["body"] def destroy_current_user(self): assert self.username is not None - self.post_data('auth/destroy', '') + self.post_data("auth/destroy", "") # Remove any records of this user USERS.pop(self.username) + # *** TESTS *** @@ -242,44 +277,47 @@ class TestPages(AuthHelper): def test_get_login_page(self): # WHEN attempting to get the login page # THEN receive an OK response code from the server - self.get_data('/login') + self.get_data("/login") def test_get_signup_pages(self): # WHEN attempting to get the signup flow # THEN receive an OK response code from the server - self.get_data('/signup') + self.get_data("/signup") def test_get_recover_page(self): # WHEN attempting to get the signup page # THEN receive an OK response code from the server - self.get_data('/recover') + self.get_data("/recover") def test_get_admin_page(self): # WHEN attempting to get the admin page # THEN receive an OK response code from the server # (Note: this only happens in a dev environment) - self.get_data('/admin') + self.get_data("/admin") def test_get_cheatsheet(self): # WHEN attempting to get the cheatsheet page # THEN receive an OK response code from the server - self.get_data('/cheatsheet') + self.get_data("/cheatsheet") def test_invalid_get_cheatsheet(self): # WHEN attempting to get the cheatsheet page with an invalid value # THEN receive an 404 response code from the server - self.get_data('/cheatsheet/123', expect_http_code=404) - self.get_data('/cheatsheet/panda', expect_http_code=404) + self.get_data("/cheatsheet/123", expect_http_code=404) + self.get_data("/cheatsheet/panda", expect_http_code=404) def test_highscore_pages(self): # WHEN trying all languages to reach the highscore page # THEN receive an OK response from the server self.given_fresh_user_is_logged_in() - body = {'email': self.user['email'], 'keyword_language': self.user['keyword_language']} + body = { + "email": self.user["email"], + "keyword_language": self.user["keyword_language"], + } for language in ALL_LANGUAGES.keys(): - body['language'] = language - self.post_data('profile', body) + body["language"] = language + self.post_data("profile", body) self.get_data("/highscores") def test_valid_country_highscore_page(self): @@ -289,12 +327,12 @@ def test_valid_country_highscore_page(self): # Add a country to the user profile body = { - 'email': self.user['email'], - 'language': self.user['language'], - 'keyword_language': self.user['keyword_language'], - 'country': 'NL' + "email": self.user["email"], + "language": self.user["language"], + "keyword_language": self.user["keyword_language"], + "country": "NL", } - self.post_data('profile', body) + self.post_data("profile", body) # Receive a valid response self.get_data("/highscores/country") @@ -308,12 +346,12 @@ def test_invalid_country_highscore_page(self): def test_valid_class_highscore_page(self): # WHEN a teacher is logged in and create a class self.given_teacher_is_logged_in() - self.post_data('class', {'name': 'class1'}) - Class = self.get_data('classes')[0] + self.post_data("class", {"name": "class1"}) + Class = self.get_data("classes")[0] # THEN a fresh user logs in and joins this class self.given_fresh_user_is_logged_in() - self.post_data('class/join', {'id': Class['id']}, expect_http_code=200) + self.post_data("class/join", {"id": Class["id"]}, expect_http_code=200) # THEN we can access the class highscore page self.get_data("/highscores/class") @@ -332,8 +370,7 @@ def test_valid_program_filtering_page(self): # THEN we can retrieve the different filtering options of the programs page filters = [ "?level=null&adventure=null", - "?level=4&adventure=null" - "?level=2&adventure=story" + "?level=4&adventure=null" "?level=2&adventure=story", ] for filter in filters: self.get_data("/programs" + filter) @@ -346,7 +383,7 @@ def test_valid_explore_filtering_page(self): filters = [ "?level=null&adventure=null&lang=null", "?level=3&adventure=parrot&lang=null", - "?level=11&adventure=fortune&lang=es" + "?level=11&adventure=fortune&lang=es", ] for filter in filters: self.get_data("/explore" + filter) @@ -356,21 +393,25 @@ def test_all_languages(self): # THEN receive an OK response from the server self.given_fresh_user_is_logged_in() - body = {'email': self.user['email'], 'keyword_language': self.user['keyword_language']} + body = { + "email": self.user["email"], + "keyword_language": self.user["keyword_language"], + } pages = [ - '/', - '/hedy', - '/landing-page', - '/tutorial', - '/explore', - '/learn-more', - '/programs', - '/my-achievements', - '/my-profile'] + "/", + "/hedy", + "/landing-page", + "/tutorial", + "/explore", + "/learn-more", + "/programs", + "/my-achievements", + "/my-profile", + ] for language in ALL_LANGUAGES.keys(): - body['language'] = language - self.post_data('profile', body) + body["language"] = language + self.post_data("profile", body) for page in pages: if page == "/hedy": @@ -382,34 +423,36 @@ def test_all_languages(self): class TestSessionVariables(AuthHelper): def test_get_session_variables(self): # WHEN getting session variables from the main environment - body = self.get_data('/session_main') + body = self.get_data("/session_main") # THEN the body should contain a `session` with `session_id` and a `proxy_enabled` field - self.assertIn('session', body) - self.assertIn('session_id', body['session']) - self.assertIn('proxy_enabled', body) + self.assertIn("session", body) + self.assertIn("session_id", body["session"]) + self.assertIn("proxy_enabled", body) - session = body['session'] - proxy_enabled = body['proxy_enabled'] + session = body["session"] + proxy_enabled = body["proxy_enabled"] # WHEN getting session variables from the test environment - test_body = self.get_data('/session_test') + test_body = self.get_data("/session_test") if not proxy_enabled: # If proxying to test is disabled, there is nothing else to test. return # THEN the body should contain a `session` with `session_id` and a `test_session` field - self.assertIn('session', test_body) - self.assertIn('session_id', test_body['session']) - self.assertIn('test_session', test_body['session']) - self.assertEquals(test_body['session']['session_id'], session['id']) + self.assertIn("session", test_body) + self.assertIn("session_id", test_body["session"]) + self.assertIn("test_session", test_body["session"]) + self.assertEquals(test_body["session"]["session_id"], session["id"]) # WHEN getting session variables from the main environment - body = self.get_data('/session_main') + body = self.get_data("/session_main") # THEN the body should have a session with a session_id that is still the # same and a `test_session` field as well - self.assertEqual(body['session']['session_id'], session['id']) - self.assertEqual(body['session']['test_session'], test_body['session']['session_id']) + self.assertEqual(body["session"]["session_id"], session["id"]) + self.assertEqual( + body["session"]["test_session"], test_body["session"]["session_id"] + ) class TestAuth(AuthHelper): @@ -418,71 +461,111 @@ def test_invalid_signups(self): username = self.make_username() # WHEN attempting signups with invalid bodies invalid_bodies = [ - '', + "", [], {}, - {'username': 1}, - {'username': 'user@me', 'password': 'foobar', 'email': 'a@a.com'}, - {'username:': 'user: me', 'password': 'foobar', 'email': 'a@a.co'}, - {'username': 't'}, - {'username': ' t '}, - {'username': username}, - {'username': username, 'password': 1}, - {'username': username, 'password': 'foo'}, - {'username': username, 'password': 'foobar'}, - {'username': username, 'password': 'foobar', 'email': 'me@something'}, - {'username': username, 'password': 'foobar', 'email': 'me@something.com', 'language': 123}, - {'username': username, 'password': 'foobar', 'email': 'me@something.com', 'language': True}, - {'username': username, 'password': 'foobar', 'email': 'me@something.com', 'heard_about': 'foo'}, - {'username': username, 'password': 'foobar', - 'email': 'me@something.com', 'heard_about': ['other_source', 'foo']}, - {'username': username, 'password': 'foobar', 'email': 'me@something.com', 'heard_about': [2]}, - {'username': username, 'password': 'foobar', 'email': 'me@something.com', 'prog_experience': [2]}, - {'username': username, 'password': 'foobar', 'email': 'me@something.com', 'prog_experience': 'foo'}, - {'username': username, 'password': 'foobar', 'email': 'me@something.com', 'experience_languages': 'python'} + {"username": 1}, + {"username": "user@me", "password": "foobar", "email": "a@a.com"}, + {"username:": "user: me", "password": "foobar", "email": "a@a.co"}, + {"username": "t"}, + {"username": " t "}, + {"username": username}, + {"username": username, "password": 1}, + {"username": username, "password": "foo"}, + {"username": username, "password": "foobar"}, + {"username": username, "password": "foobar", "email": "me@something"}, + { + "username": username, + "password": "foobar", + "email": "me@something.com", + "language": 123, + }, + { + "username": username, + "password": "foobar", + "email": "me@something.com", + "language": True, + }, + { + "username": username, + "password": "foobar", + "email": "me@something.com", + "heard_about": "foo", + }, + { + "username": username, + "password": "foobar", + "email": "me@something.com", + "heard_about": ["other_source", "foo"], + }, + { + "username": username, + "password": "foobar", + "email": "me@something.com", + "heard_about": [2], + }, + { + "username": username, + "password": "foobar", + "email": "me@something.com", + "prog_experience": [2], + }, + { + "username": username, + "password": "foobar", + "email": "me@something.com", + "prog_experience": "foo", + }, + { + "username": username, + "password": "foobar", + "email": "me@something.com", + "experience_languages": "python", + }, ] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server - self.post_data('auth/signup', invalid_body, expect_http_code=400) + self.post_data("auth/signup", invalid_body, expect_http_code=400) def test_signup(self): # GIVEN a valid username and signup body username = self.make_username() user = { - 'username': username, - 'email': username + '@hedy.com', - 'password': 'foobar', - 'password_repeat': 'foobar', - 'language': 'nl', - 'keyword_language': 'en', - 'heard_about': 'from_another_teacher', - 'agree_terms': 'yes'} + "username": username, + "email": username + "@hedy.com", + "password": "foobar", + "password_repeat": "foobar", + "language": "nl", + "keyword_language": "en", + "heard_about": "from_another_teacher", + "agree_terms": "yes", + } # WHEN signing up a new user # THEN receive an OK response code from the server - body = self.post_data('auth/signup', user) + body = self.post_data("auth/signup", user) # THEN receive a body containing a token self.assertIsInstance(body, dict) - self.assertIsInstance(body['token'], str) + self.assertIsInstance(body["token"], str) # FINALLY Store the user and its token for upcoming tests - user['verify_token'] = body['token'] + user["verify_token"] = body["token"] USERS[username] = user def test_invalid_login(self): # WHEN attempting logins with invalid bodies invalid_bodies = [ - '', + "", [], {}, - {'username': 1}, - {'username': 'user@me'}, - {'username:': 'user: me'} + {"username": 1}, + {"username": "user@me"}, + {"username:": "user: me"}, ] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server - self.post_data('auth/login', invalid_body, expect_http_code=400) + self.post_data("auth/login", invalid_body, expect_http_code=400) def test_login(self): # GIVEN an existing user @@ -490,18 +573,19 @@ def test_login(self): # WHEN logging in the user # THEN receive an OK response code from the server - headers = self.post_data('auth/login', - {'username': self.username, - 'password': self.user['password']}, - return_headers=True) + headers = self.post_data( + "auth/login", + {"username": self.username, "password": self.user["password"]}, + return_headers=True, + ) # THEN validate the cookie sent in the response - self.assertIsInstance(headers['Set-Cookie'], str) - hedy_cookie = self.get_hedy_cookie(headers['Set-Cookie']) + self.assertIsInstance(headers["Set-Cookie"], str) + hedy_cookie = self.get_hedy_cookie(headers["Set-Cookie"]) self.assertNotEqual(hedy_cookie, None) - self.assertEqual(hedy_cookie['httponly'], True) - self.assertEqual(hedy_cookie['path'], '/') - self.assertEqual(hedy_cookie['samesite'], 'Lax,') + self.assertEqual(hedy_cookie["httponly"], True) + self.assertEqual(hedy_cookie["path"], "/") + self.assertEqual(hedy_cookie["samesite"], "Lax,") def test_invalid_verify_email(self): # GIVEN a new user @@ -511,32 +595,32 @@ def test_invalid_verify_email(self): # WHEN submitting invalid verifications invalid_verifications = [ # Missing token - {'username': self.username}, + {"username": self.username}, # Missing username - {'token': self.user['verify_token']}, + {"token": self.user["verify_token"]}, ] for invalid_verification in invalid_verifications: # THEN receive an invalid response code from the server self.get_data( - 'auth/verify?' + - urllib.parse.urlencode(invalid_verification), - expect_http_code=400) + "auth/verify?" + urllib.parse.urlencode(invalid_verification), + expect_http_code=400, + ) # WHEN submitting well-formed verifications with invalid values incorrect_verifications = [ # Invalid username - {'username': 'foobar', 'token': self.user['verify_token']}, + {"username": "foobar", "token": self.user["verify_token"]}, # Invalid token - {'username': self.username, 'token': 'foobar'} + {"username": self.username, "token": "foobar"}, ] for incorrect_verification in incorrect_verifications: # THEN receive a forbidden response code from the server self.get_data( - 'auth/verify?' + - urllib.parse.urlencode(incorrect_verification), - expect_http_code=403) + "auth/verify?" + urllib.parse.urlencode(incorrect_verification), + expect_http_code=403, + ) def test_verify_email(self): # GIVEN a new user @@ -546,37 +630,37 @@ def test_verify_email(self): # WHEN attepting to verify the user # THEN receive a redirect from the server taking us to `/landing-page` headers = self.get_data( - 'auth/verify?' + - urllib.parse.urlencode( - { - 'username': self.username, - 'token': self.user['verify_token']}), + "auth/verify?" + + urllib.parse.urlencode( + {"username": self.username, "token": self.user["verify_token"]} + ), expect_http_code=302, - return_headers=True) - self.assertEqual(headers['location'], '/landing-page') + return_headers=True, + ) + self.assertEqual(headers["location"], "/landing-page") # WHEN attepting to verify the user again (the operation should be idempotent) # THEN (again) receive a redirect from the server taking us to `/landing-page` headers = self.get_data( - 'auth/verify?' + - urllib.parse.urlencode( - { - 'username': self.username, - 'token': self.user['verify_token']}), + "auth/verify?" + + urllib.parse.urlencode( + {"username": self.username, "token": self.user["verify_token"]} + ), expect_http_code=302, - return_headers=True) - self.assertEqual(headers['location'], '/landing-page') + return_headers=True, + ) + self.assertEqual(headers["location"], "/landing-page") # WHEN retrieving profile to see that the user is no longer marked with # `verification_pending` self.given_specific_user_is_logged_in(self.username) - profile = self.get_data('profile') + profile = self.get_data("profile") # THEN check that the `verification_pending` has been removed from the user profile - self.assertNotIn('verification_pending', profile) + self.assertNotIn("verification_pending", profile) # FINALLY remove token from user since it's already been used. - self.user.pop('verify_token') + self.user.pop("verify_token") def test_logout(self): # GIVEN a logged in user @@ -584,11 +668,11 @@ def test_logout(self): # WHEN logging out the user # THEN receive an OK response code from the server - self.post_data('auth/logout', '') + self.post_data("auth/logout", "") # WHEN retrieving the user profile with the same cookie # THEN receive a forbidden response code from the server - self.get_data('profile', expect_http_code=403) + self.get_data("profile", expect_http_code=403) def test_destroy_account(self): # GIVEN a logged in user @@ -600,7 +684,7 @@ def test_destroy_account(self): # WHEN retrieving the profile of the user # THEN receive a forbidden response code from the server - self.get_data('profile', expect_http_code=403) + self.get_data("profile", expect_http_code=403) def test_invalid_change_password(self): # GIVEN a logged in user @@ -608,53 +692,68 @@ def test_invalid_change_password(self): # WHEN attempting change password with invalid bodies invalid_bodies = [ - '', + "", [], {}, - {'old_password': 123456}, - {'old_password': 'pass1'}, - {'old_password': 'pass1', 'new-password': 123456}, - {'old_password': 'pass1', 'new-password': 'short'}, - {'old_password': 'pass1', 'new-password': 123456, 'password_repeat': 'panda'}, - {'old_password': 'pass1', 'new-password': 'panda'}, + {"old_password": 123456}, + {"old_password": "pass1"}, + {"old_password": "pass1", "new-password": 123456}, + {"old_password": "pass1", "new-password": "short"}, + { + "old_password": "pass1", + "new-password": 123456, + "password_repeat": "panda", + }, + {"old_password": "pass1", "new-password": "panda"}, ] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server - self.post_data('auth/change_password', invalid_body, expect_http_code=400) + self.post_data("auth/change_password", invalid_body, expect_http_code=400) # WHEN attempting to change password without sending the correct old password # THEN receive an invalid response code from the server - body = {'old_password': 'pass1', 'new-password': '123456', 'password_repeat': '123456'} - self.post_data('auth/change_password', body, expect_http_code=403) + body = { + "old_password": "pass1", + "new-password": "123456", + "password_repeat": "123456", + } + self.post_data("auth/change_password", body, expect_http_code=403) def test_change_password(self): # GIVEN a logged in user self.given_user_is_logged_in() # WHEN attempting to change the user's password - new_password = 'pas1234' + new_password = "pas1234" # THEN receive an OK response code from the server - self.post_data('auth/change_password', - {'old_password': self.user['password'], - 'new-password': 'pas1234', - 'password_repeat': 'pas1234'}) + self.post_data( + "auth/change_password", + { + "old_password": self.user["password"], + "new-password": "pas1234", + "password_repeat": "pas1234", + }, + ) # WHEN attempting to login with old password # THEN receive a forbidden response code from the server - self.post_data('auth/login', - {'username': self.username, - 'password': self.user['password']}, - expect_http_code=403) + self.post_data( + "auth/login", + {"username": self.username, "password": self.user["password"]}, + expect_http_code=403, + ) # GIVEN the same user # WHEN attempting to login with new password # THEN receive an OK response code from the server - self.post_data('auth/login', {'username': self.username, 'password': new_password}) + self.post_data( + "auth/login", {"username": self.username, "password": new_password} + ) # FINALLY update password on user - self.user['password'] = new_password + self.user["password"] = new_password def test_profile_get(self): # GIVEN a new user @@ -663,16 +762,16 @@ def test_profile_get(self): # WHEN retrieving the user profile # THEN receive an OK response code from the server - profile = self.get_data('profile') + profile = self.get_data("profile") # THEN check that the fields returned by the server have the correct values self.assertIsInstance(profile, dict) - self.assertEqual(profile['username'], self.username), - self.assertEqual(profile['email'], self.user['email']), - self.assertEqual(profile['verification_pending'], True) - self.assertIsInstance(profile['student_classes'], list) - self.assertEqual(len(profile['student_classes']), 0) - self.assertIsInstance(profile['session_expires_at'], int) + self.assertEqual(profile["username"], self.username), + self.assertEqual(profile["email"], self.user["email"]), + self.assertEqual(profile["verification_pending"], True) + self.assertIsInstance(profile["student_classes"], list) + self.assertEqual(len(profile["student_classes"]), 0) + self.assertIsInstance(profile["session_expires_at"], int) def test_invalid_profile_modify(self): # GIVEN a logged in user @@ -680,31 +779,31 @@ def test_invalid_profile_modify(self): # WHEN attempting profile modifications with invalid bodies invalid_bodies = [ - '', + "", [], - {'email': 'foobar'}, - {'birth_year': 'a'}, - {'birth_year': 20}, - {'gender': 0}, - {'gender': 'a'}, - {'language': True}, - {'language': 123}, - {'keyword_language': True}, - {'keyword_language': 123}, + {"email": "foobar"}, + {"birth_year": "a"}, + {"birth_year": 20}, + {"gender": 0}, + {"gender": "a"}, + {"language": True}, + {"language": 123}, + {"keyword_language": True}, + {"keyword_language": 123}, ] for invalid_body in invalid_bodies: # Create a valid body that we overwrite with invalid values if isinstance(invalid_body, dict): body = { - 'email': self.user['email'], - 'language': self.user['language'], - 'keyword_language': self.user['keyword_language'] + "email": self.user["email"], + "language": self.user["language"], + "keyword_language": self.user["keyword_language"], } body.update(invalid_body) invalid_body = body # THEN receive an invalid response code from the server - self.post_data('profile', invalid_body, expect_http_code=400) + self.post_data("profile", invalid_body, expect_http_code=400) def test_profile_modify(self): # GIVEN a new user @@ -712,24 +811,20 @@ def test_profile_modify(self): self.given_fresh_user_is_logged_in() # WHEN submitting valid profile changes - profile_changes = { - 'birth_year': 1989, - 'country': 'NL', - 'gender': 'o' - } + profile_changes = {"birth_year": 1989, "country": "NL", "gender": "o"} body = { - 'email': self.user['email'], - 'language': self.user['language'], - 'keyword_language': self.user['keyword_language'] + "email": self.user["email"], + "language": self.user["language"], + "keyword_language": self.user["keyword_language"], } for key in profile_changes: body[key] = profile_changes[key] # THEN receive an OK response code from the server - self.post_data('profile', body) + self.post_data("profile", body) # WHEN retrieving the profile - profile = self.get_data('profile') + profile = self.get_data("profile") # THEN confirm that our modification has been stored by the server and # returned in the latest version of the profile self.assertEqual(profile[key], profile_changes[key]) @@ -737,18 +832,22 @@ def test_profile_modify(self): # WHEN updating the user's email # (we check email change separately since it involves a flow with a token) # THEN receive an OK response code from the server - new_email = self.username + '@newhedy.com' - body = self.post_data('profile', - {'email': new_email, - 'language': self.user['language'], - 'keyword_language': self.user['keyword_language']}) + new_email = self.username + "@newhedy.com" + body = self.post_data( + "profile", + { + "email": new_email, + "language": self.user["language"], + "keyword_language": self.user["keyword_language"], + }, + ) # THEN confirm that the server replies with an email verification token - self.assertIsInstance(body['token'], str) + self.assertIsInstance(body["token"], str) # FINALLY update the email & email verification token on user - self.user['email'] = new_email - self.user['verify_token'] = body['token'] + self.user["email"] = new_email + self.user["verify_token"] = body["token"] def test_invalid_change_language(self): # GIVEN a logged in user @@ -756,83 +855,65 @@ def test_invalid_change_language(self): # WHEN trying to update the profile with an invalid language body = { - 'email': self.user['email'], - 'language': 'abc', - 'keyword_language': self.user['keyword_language'] + "email": self.user["email"], + "language": "abc", + "keyword_language": self.user["keyword_language"], } # THEN receive an invalid response code from the server - self.post_data('profile', body, expect_http_code=400) + self.post_data("profile", body, expect_http_code=400) def test_valid_change_language(self): # GIVEN a logged in user self.given_user_is_logged_in() # WHEN trying to update the profile with a valid language - body = { - 'email': self.user['email'], - 'language': 'nl', - 'keyword_language': 'nl' - } + body = {"email": self.user["email"], "language": "nl", "keyword_language": "nl"} # THEN receive a valid response code from the server - self.post_data('profile', body, expect_http_code=200) + self.post_data("profile", body, expect_http_code=200) # WHEN trying to retrieve the current profile - profile = self.get_data('profile') + profile = self.get_data("profile") # THEN verify that the language is successfully changed - self.assertEqual(profile['language'], body['language']) + self.assertEqual(profile["language"], body["language"]) def test_invalid_keyword_language(self): # GIVEN a logged in user self.given_user_is_logged_in() # WHEN trying to update the profile with an invalid keyword language - invalid_keyword_language = [ - 'nl', - 123, - 'panda' - ] + invalid_keyword_language = ["nl", 123, "panda"] - body = { - 'email': self.user['email'], - 'language': 'en' - } + body = {"email": self.user["email"], "language": "en"} for invalid_lang in invalid_keyword_language: - body['keyword_language'] = invalid_lang + body["keyword_language"] = invalid_lang # THEN receive an invalid response code from the server - self.post_data('profile', body, expect_http_code=400) + self.post_data("profile", body, expect_http_code=400) def test_valid_keyword_language(self): # GIVEN a logged in user self.given_user_is_logged_in() # WHEN trying to update the profile with a valid keyword language - body = { - 'email': self.user['email'], - 'language': 'nl', - 'keyword_language': 'nl' - } + body = {"email": self.user["email"], "language": "nl", "keyword_language": "nl"} # THEN receive a valid response code from the server - self.post_data('profile', body, expect_http_code=200) + self.post_data("profile", body, expect_http_code=200) def test_invalid_recover_password(self): # GIVEN an existing user self.given_any_user() # WHEN attempting a password recovery with invalid bodies - invalid_bodies = [ - '', - [], - {}, - {'username': 1} - ] + invalid_bodies = ["", [], {}, {"username": 1}] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server - self.post_data('auth/recover', invalid_body, expect_http_code=400) + self.post_data("auth/recover", invalid_body, expect_http_code=400) # WHEN attempting a password recovery with a non-existing username # THEN receive a forbidden response code from the server - self.post_data('auth/recover', {'username': self.make_username()}, expect_http_code=403) + self.post_data( + "auth/recover", {"username": self.make_username()}, expect_http_code=403 + ) def test_recover_password(self): # GIVEN an existing user @@ -840,9 +921,9 @@ def test_recover_password(self): # WHEN attempting a password recovery # THEN receive an OK response code from the server - body = self.post_data('auth/recover', {'username': self.username}) + body = self.post_data("auth/recover", {"username": self.username}) # THEN check that we have received a password recovery token from the server - self.assertIsInstance(body['token'], str) + self.assertIsInstance(body["token"], str) def test_invalid_reset_password(self): # GIVEN an existing user @@ -850,116 +931,163 @@ def test_invalid_reset_password(self): # WHEN attempting a password reset with invalid bodies invalid_bodies = [ - '', + "", [], {}, - {'username': 1}, - {'username': 'foobar', 'token': 1}, - {'username': 'foobar', 'token': 'some'}, - {'username': 'foobar', 'token': 'some', 'password': 1}, - {'username': 'foobar', 'token': 'some', 'password': 'short'}, - {'username': 'foobar', 'token': 'some', 'password': 'short', 'password_repeat': 123}, - {'username': 'foobar', 'token': 'some', 'password_repeat': 'panda123'} + {"username": 1}, + {"username": "foobar", "token": 1}, + {"username": "foobar", "token": "some"}, + {"username": "foobar", "token": "some", "password": 1}, + {"username": "foobar", "token": "some", "password": "short"}, + { + "username": "foobar", + "token": "some", + "password": "short", + "password_repeat": 123, + }, + {"username": "foobar", "token": "some", "password_repeat": "panda123"}, ] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server - self.post_data('auth/reset', invalid_body, expect_http_code=400) + self.post_data("auth/reset", invalid_body, expect_http_code=400) # WHEN attempting a password reset with an invalid token # THEN receive a forbidden response code from the server - self.post_data('auth/reset', - {'username': self.username, - 'password': '123456', - 'password_repeat': '123456', - 'token': 'foobar'}, - expect_http_code=403) + self.post_data( + "auth/reset", + { + "username": self.username, + "password": "123456", + "password_repeat": "123456", + "token": "foobar", + }, + expect_http_code=403, + ) def test_reset_password(self): # GIVEN an existing user self.given_any_user() # WHEN attempting a password reset with a valid username & token combination - new_password = 'pas1234' - recover_token = self.post_data('auth/recover', {'username': self.username})['token'] + new_password = "pas1234" + recover_token = self.post_data("auth/recover", {"username": self.username})[ + "token" + ] # THEN receive an OK response code from the server - self.post_data('auth/reset', {'username': self.username, 'password': new_password, - 'password_repeat': new_password, 'token': recover_token}) + self.post_data( + "auth/reset", + { + "username": self.username, + "password": new_password, + "password_repeat": new_password, + "token": recover_token, + }, + ) # WHEN attempting a login with the new password # THEN receive an OK response code from the server - self.post_data('auth/login', {'username': self.username, 'password': new_password}) + self.post_data( + "auth/login", {"username": self.username, "password": new_password} + ) # FINALLY update user's password and attempt login with new password - self.user['password'] = new_password + self.user["password"] = new_password def test_invalid_public_profile(self): # GIVEN a logged in user self.given_user_is_logged_in() # Create a program -> make sure it is not public - program = {'code': 'hello world', 'name': 'program 1', 'level': 1, 'shared': False} - program_id = self.post_data('programs', program)['id'] + program = { + "code": "hello world", + "name": "program 1", + "level": 1, + "shared": False, + } + program_id = self.post_data("programs", program)["id"] # WHEN attempting to create a public profile with invalid bodies invalid_bodies = [ - '', + "", [], {}, - {'image': 123456}, - {'image': '123'}, - {'image': '123', 'personal_text': 123}, - {'image': '123', 'personal_text': 123}, - {'image': '123', 'personal_text': 123, 'favourite_program': 123}, - {'image': '123', 'personal_text': 'Welcome to my profile!', 'favourite_program': 123}, - {'image': '5', 'personal_text': 'Welcome to my profile!', 'favourite_program': 123}, - {'image': '5', 'personal_text': 'Welcome to my profile!', 'favourite_program': "abcdefghi"}, - {'image': '5', 'personal_text': 'Welcome to my profile!', 'favourite_program': program_id}, + {"image": 123456}, + {"image": "123"}, + {"image": "123", "personal_text": 123}, + {"image": "123", "personal_text": 123}, + {"image": "123", "personal_text": 123, "favourite_program": 123}, + { + "image": "123", + "personal_text": "Welcome to my profile!", + "favourite_program": 123, + }, + { + "image": "5", + "personal_text": "Welcome to my profile!", + "favourite_program": 123, + }, + { + "image": "5", + "personal_text": "Welcome to my profile!", + "favourite_program": "abcdefghi", + }, + { + "image": "5", + "personal_text": "Welcome to my profile!", + "favourite_program": program_id, + }, ] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server - self.post_data('auth/public_profile', invalid_body, expect_http_code=400) + self.post_data("auth/public_profile", invalid_body, expect_http_code=400) def test_public_profile_without_favourite(self): # GIVEN a logged in user self.given_user_is_logged_in() - public_profile = {'image': '9', 'personal_text': 'welcome to my profile!'} + public_profile = {"image": "9", "personal_text": "welcome to my profile!"} # WHEN creating a new public profile # THEN receive an OK response code from the server - self.post_data('auth/public_profile', public_profile, expect_http_code=200) + self.post_data("auth/public_profile", public_profile, expect_http_code=200) def test_public_profile_with_favourite(self): # GIVEN a logged in user self.given_user_is_logged_in() # Create a program that is public -> can be set as favourite on the public profile - program = {'code': 'hello world', 'name': 'program 1', 'level': 1, 'shared': True} - program_id = self.post_data('programs', program)['id'] + program = { + "code": "hello world", + "name": "program 1", + "level": 1, + "shared": True, + } + program_id = self.post_data("programs", program)["id"] public_profile = { - 'image': '9', - 'personal_text': 'welcome to my profile!', - 'favourite_program': program_id} + "image": "9", + "personal_text": "welcome to my profile!", + "favourite_program": program_id, + } # WHEN creating a new public profile with favourite program # THEN receive an OK response code from the server - self.post_data('auth/public_profile', public_profile, expect_http_code=200) + self.post_data("auth/public_profile", public_profile, expect_http_code=200) def test_destroy_public_profile(self): # GIVEN a logged in user self.given_user_is_logged_in() - public_profile = {'image': '9', 'personal_text': 'welcome to my profile!'} + public_profile = {"image": "9", "personal_text": "welcome to my profile!"} # WHEN creating a new public profile with favourite program # THEN receive an OK response code from the server - self.post_data('auth/public_profile', public_profile, expect_http_code=200) + self.post_data("auth/public_profile", public_profile, expect_http_code=200) # WHEN destroying the public profile # THEN receive an OK response from the server - self.post_data('auth/destroy_public', public_profile, expect_http_code=200) + self.post_data("auth/destroy_public", public_profile, expect_http_code=200) class TestProgram(AuthHelper): @@ -969,7 +1097,7 @@ def test_invalid_get_programs(self): # WHEN retrieving own programs but without sending a cookie # THEN receive a forbidden response code from the server - self.get_data('programs/list', expect_http_code=403, no_cookie=True) + self.get_data("programs/list", expect_http_code=403, no_cookie=True) def test_get_programs(self): # GIVEN a logged in user @@ -977,11 +1105,11 @@ def test_get_programs(self): # WHEN retrieving own programs sending a cookie # THEN receive an OK response code from the server - body = self.get_data('programs/list') + body = self.get_data("programs/list") # THEN verify that the server sent a body that is an object of the shape `{programs:[...]}`. self.assertIsInstance(body, dict) - self.assertIsInstance(body['programs'], list) + self.assertIsInstance(body["programs"], list) def test_invalid_create_program(self): # GIVEN a logged in user @@ -989,31 +1117,35 @@ def test_invalid_create_program(self): # WHEN attempting to create an invalid program invalid_bodies = [ - '', + "", [], {}, - {'code': 1}, - {'code': ['1']}, - {'code': 'hello world'}, - {'code': 'hello world', 'name': 1}, - {'code': 'hello world', 'name': 'program 1'}, - {'code': 'hello world', 'name': 'program 1', 'level': '1'}, - {'code': 'hello world', 'name': 'program 1', 'level': 1, 'adventure_name': 1}, + {"code": 1}, + {"code": ["1"]}, + {"code": "hello world"}, + {"code": "hello world", "name": 1}, + {"code": "hello world", "name": "program 1"}, + {"code": "hello world", "name": "program 1", "level": "1"}, + { + "code": "hello world", + "name": "program 1", + "level": 1, + "adventure_name": 1, + }, ] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server - self.post_data('programs', invalid_body, expect_http_code=400) + self.post_data("programs", invalid_body, expect_http_code=400) # WHEN submitting a program without being logged in # THEN receive a forbidden response code from the server - self.post_data('programs', - {'code': 'hello world', - 'name': 'program 1', - 'level': 1, - 'shared': False}, - expect_http_code=403, - no_cookie=True) + self.post_data( + "programs", + {"code": "hello world", "name": "program 1", "level": 1, "shared": False}, + expect_http_code=403, + no_cookie=True, + ) def test_create_program(self): # GIVEN a new user @@ -1021,16 +1153,21 @@ def test_create_program(self): self.given_fresh_user_is_logged_in() # WHEN submitting a valid program - program = {'code': 'hello world', 'name': 'program 1', 'level': 1, 'shared': False} + program = { + "code": "hello world", + "name": "program 1", + "level": 1, + "shared": False, + } # THEN receive an OK response code from the server - program = self.post_data('programs', program) + program = self.post_data("programs", program) # THEN verify that the returned program has both a name and an id self.assertIsInstance(program, dict) - self.assertIsInstance(program['id'], str) - self.assertIsInstance(program['name'], str) + self.assertIsInstance(program["id"], str) + self.assertIsInstance(program["name"], str) # WHEN retrieving programs after saving a program - saved_programs = self.get_data('programs/list')['programs'] + saved_programs = self.get_data("programs/list")["programs"] print(saved_programs) # THEN verify that the program we just saved is in the list @@ -1041,7 +1178,11 @@ def test_create_program(self): for key in program: # WHEN we create a program an achievement is achieved, being in the response but not the saved_program if key not in keys_to_ignore: - self.assertEqual(program.get(key, None), saved_program.get(key, None), f'Difference on key {key}') + self.assertEqual( + program.get(key, None), + saved_program.get(key, None), + f"Difference on key {key}", + ) def test_invalid_make_program_public(self): # GIVEN a logged in user @@ -1049,112 +1190,151 @@ def test_invalid_make_program_public(self): # WHEN attempting to share a program with an invalid body invalid_bodies = [ - '', + "", [], {}, - {'code': 1}, - {'code': ['1']}, - {'code': 'hello world'}, - {'code': 'hello world', 'name': 1}, - {'code': 'hello world', 'name': 'program 1'}, - {'code': 'hello world', 'name': 'program 1', 'level': '1'}, - {'code': 'hello world', 'name': 'program 1', 'level': 1, 'adventure_name': 1}, + {"code": 1}, + {"code": ["1"]}, + {"code": "hello world"}, + {"code": "hello world", "name": 1}, + {"code": "hello world", "name": "program 1"}, + {"code": "hello world", "name": "program 1", "level": "1"}, + { + "code": "hello world", + "name": "program 1", + "level": 1, + "adventure_name": 1, + }, ] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server - self.post_data('programs/share', invalid_body, expect_http_code=400) + self.post_data("programs/share", invalid_body, expect_http_code=400) # WHEN sharing a program without being logged in # THEN receive a forbidden response code from the server - self.post_data('programs/share', - {'id': '123456', - 'public': True}, - expect_http_code=403, - no_cookie=True) + self.post_data( + "programs/share", + {"id": "123456", "public": True}, + expect_http_code=403, + no_cookie=True, + ) # WHEN sharing a program that does not exist # THEN receive a not found response code from the server - self.post_data('programs/share', {'id': '123456', 'public': True}, expect_http_code=404) + self.post_data( + "programs/share", {"id": "123456", "public": True}, expect_http_code=404 + ) def test_valid_make_program_public(self): # GIVEN a logged in user with at least one program self.given_user_is_logged_in() - program = {'code': 'hello world', 'name': 'program 1', 'level': 1, 'shared': False} - program_id = self.post_data('programs', program)['id'] + program = { + "code": "hello world", + "name": "program 1", + "level": 1, + "shared": False, + } + program_id = self.post_data("programs", program)["id"] # WHEN making a program public # THEN receive an OK response code from the server - self.post_data('programs/share', {'id': program_id, 'public': True, }) + self.post_data( + "programs/share", + { + "id": program_id, + "public": True, + }, + ) - saved_programs = self.get_data('programs/list')['programs'] + saved_programs = self.get_data("programs/list")["programs"] for program in saved_programs: - if program['id'] != program_id: + if program["id"] != program_id: continue # THEN the program must have its `public` field enabled - self.assertEqual(program['public'], 1) + self.assertEqual(program["public"], 1) # GIVEN another user self.given_fresh_user_is_logged_in() # WHEN requesting a public program # THEN receive an OK response code from the server - self.get_data('hedy/1/' + program_id, expect_http_code=200) + self.get_data("hedy/1/" + program_id, expect_http_code=200) def test_valid_make_program_private(self): # GIVEN a logged in user with at least one public program self.given_user_is_logged_in() - program = {'code': 'hello world', 'name': 'program 1', 'level': 1, 'shared': False} - program_id = self.post_data('programs', program)['id'] - self.post_data('programs/share', {'id': program_id, 'public': True}) + program = { + "code": "hello world", + "name": "program 1", + "level": 1, + "shared": False, + } + program_id = self.post_data("programs", program)["id"] + self.post_data("programs/share", {"id": program_id, "public": True}) # WHEN making a program private # THEN receive an OK response code from the server - self.post_data('programs/share', {'id': program_id, 'public': False}) + self.post_data("programs/share", {"id": program_id, "public": False}) - saved_programs = self.get_data('programs/list')['programs'] + saved_programs = self.get_data("programs/list")["programs"] for program in saved_programs: - if program['id'] != program_id: + if program["id"] != program_id: continue # THEN the program must have a '0' value for Public - self.assertEqual(program['public'], 0) + self.assertEqual(program["public"], 0) # GIVEN another user self.given_fresh_user_is_logged_in() # WHEN requesting a public program # THEN receive a not found response code from the server - self.get_data('hedy/1/' + program_id, expect_http_code=404) + self.get_data("hedy/1/" + program_id, expect_http_code=404) def test_invalid_delete_program(self): # GIVEN a logged in user with at least one program self.given_user_is_logged_in() - program = {'code': 'hello world', 'name': 'program 1', 'level': 1, 'shared': False} - self.post_data('programs', program)['id'] - program_id = '123456' + program = { + "code": "hello world", + "name": "program 1", + "level": 1, + "shared": False, + } + self.post_data("programs", program)["id"] + program_id = "123456" # WHEN deleting a program that does not exist # THEN receive a not found response code from the server - self.post_data('programs/delete/', {'id': program_id}, expect_http_code=404) + self.post_data("programs/delete/", {"id": program_id}, expect_http_code=404) def test_valid_delete_program(self): # GIVEN a logged in user with at least one program self.given_user_is_logged_in() - program = {'code': 'hello world', 'name': 'program 1', 'level': 1, 'shared': False} - program_id = self.post_data('programs', program)['id'] + program = { + "code": "hello world", + "name": "program 1", + "level": 1, + "shared": False, + } + program_id = self.post_data("programs", program)["id"] # WHEN deleting a program # THEN receive an OK response code from the server - self.post_data('programs/delete/', {'id': program_id}, return_headers=True) + self.post_data("programs/delete/", {"id": program_id}, return_headers=True) - saved_programs = self.get_data('programs/list')['programs'] + saved_programs = self.get_data("programs/list")["programs"] for program in saved_programs: # THEN the program should not be any longer in the list of programs - self.assertNotEqual(program['id'], program_id) + self.assertNotEqual(program["id"], program_id) def test_destroy_account_with_programs(self): # GIVEN a logged in user with at least one program self.given_user_is_logged_in() - program = {'code': 'hello world', 'name': 'program 1', 'level': 1, 'shared': False} - self.post_data('programs', program)['id'] + program = { + "code": "hello world", + "name": "program 1", + "level": 1, + "shared": False, + } + self.post_data("programs", program)["id"] # WHEN deleting the user account # THEN receive an OK response code from the server @@ -1169,7 +1349,7 @@ def test_invalid_create_class(self): # WHEN creating a class without teacher permissions # THEN receive a forbidden response code from the server - self.post_data('class', {'name': 'class1'}, expect_http_code=403) + self.post_data("class", {"name": "class1"}, expect_http_code=403) # WHEN marking the user as teacher self.make_current_user_teacher() @@ -1177,17 +1357,11 @@ def test_invalid_create_class(self): # GIVEN a user with teacher permissions # WHEN attempting to create a class with an invalid body - invalid_bodies = [ - '', - [], - {}, - {'name': 1}, - {'name': ['foobar']} - ] + invalid_bodies = ["", [], {}, {"name": 1}, {"name": ["foobar"]}] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server - self.post_data('class', invalid_body, expect_http_code=400) + self.post_data("class", invalid_body, expect_http_code=400) def test_create_class(self): # GIVEN a user with teacher permissions @@ -1195,7 +1369,7 @@ def test_create_class(self): self.given_fresh_teacher_is_logged_in() # WHEN retrieving the list of classes - class_list = self.get_data('classes') + class_list = self.get_data("classes") # THEN receive a body containing an empty list self.assertIsInstance(class_list, list) @@ -1203,190 +1377,203 @@ def test_create_class(self): # WHEN creating a class # THEN receive an OK response code with the server - self.post_data('class', {'name': 'class1'}) + self.post_data("class", {"name": "class1"}) # GIVEN a class already saved # WHEN retrieving the list of classes - class_list = self.get_data('classes') + class_list = self.get_data("classes") # THEN receive a body containing an list with one element self.assertEqual(len(class_list), 1) # THEN validate the fields of the class Class = class_list[0] self.assertIsInstance(Class, dict) - self.assertIsInstance(Class['id'], str) - self.assertIsInstance(Class['date'], int) - self.assertIsInstance(Class['link'], str) - self.assertEqual(Class['name'], 'class1') - self.assertIsInstance(Class['students'], list) - self.assertEqual(len(Class['students']), 0) - self.assertEqual(Class['teacher'], self.username) + self.assertIsInstance(Class["id"], str) + self.assertIsInstance(Class["date"], int) + self.assertIsInstance(Class["link"], str) + self.assertEqual(Class["name"], "class1") + self.assertIsInstance(Class["students"], list) + self.assertEqual(len(Class["students"]), 0) + self.assertEqual(Class["teacher"], self.username) # WHEN retrieving the class # THEN receive an OK response code from the server - Class = self.get_data('for-teachers/class/' + Class['id']) + Class = self.get_data("for-teachers/class/" + Class["id"]) # THEN validate the fields of the class self.assertIsInstance(Class, dict) - self.assertIsInstance(Class['id'], str) - self.assertIsInstance(Class['link'], str) - self.assertEqual(Class['name'], 'class1') - self.assertIsInstance(Class['students'], list) - self.assertEqual(len(Class['students']), 0) + self.assertIsInstance(Class["id"], str) + self.assertIsInstance(Class["link"], str) + self.assertEqual(Class["name"], "class1") + self.assertIsInstance(Class["students"], list) + self.assertEqual(len(Class["students"]), 0) def test_invalid_update_class(self): # GIVEN a user with teacher permissions and a class self.given_teacher_is_logged_in() - self.post_data('class', {'name': 'class1'}) - Class = self.get_data('classes')[0] + self.post_data("class", {"name": "class1"}) + Class = self.get_data("classes")[0] # WHEN attempting to update a class with no cookie # THEN receive a forbidden status code from the server - self.post_data('class/' + Class['id'], - {'name': 'class2'}, - expect_http_code=403, - put_data=True, - no_cookie=True) + self.post_data( + "class/" + Class["id"], + {"name": "class2"}, + expect_http_code=403, + put_data=True, + no_cookie=True, + ) # WHEN attempting to update a class that does not exist # THEN receive a not found status code from the server - self.post_data('class/foo', {'name': 'class2'}, expect_http_code=404, put_data=True) + self.post_data( + "class/foo", {"name": "class2"}, expect_http_code=404, put_data=True + ) # WHEN attempting to update a class with an invalid body - invalid_bodies = [ - '', - [], - {}, - {'name': 1}, - {'name': ['foobar']} - ] + invalid_bodies = ["", [], {}, {"name": 1}, {"name": ["foobar"]}] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server self.post_data( - 'class/' + Class['id'], + "class/" + Class["id"], invalid_body, expect_http_code=400, - put_data=True) + put_data=True, + ) def test_update_class(self): # GIVEN a user with teacher permissions and a class self.given_teacher_is_logged_in() - self.post_data('class', {'name': 'class1'}) - Class = self.get_data('classes')[0] + self.post_data("class", {"name": "class1"}) + Class = self.get_data("classes")[0] # WHEN attempting to update a class # THEN receive an OK status code from the server - self.post_data('class/' + Class['id'], {'name': 'class2'}, put_data=True) + self.post_data("class/" + Class["id"], {"name": "class2"}, put_data=True) # WHEN retrieving the class # THEN receive an OK response code from the server - Class = self.get_data('for-teachers/class/' + Class['id']) + Class = self.get_data("for-teachers/class/" + Class["id"]) # THEN the name of the class should be updated - self.assertEqual(Class['name'], 'class2') + self.assertEqual(Class["name"], "class2") def test_copy_class(self): # GIVEN a user with teacher permissions and a class self.given_teacher_is_logged_in() - self.post_data('class', {'name': 'class1'}) - Class = self.get_data('classes')[0] + self.post_data("class", {"name": "class1"}) + Class = self.get_data("classes")[0] # WHEN attempting to duplicate a class # THEN receive an OK status code from the server - self.post_data('/duplicate_class', {'id': Class['id'], 'name': 'class2'}) + self.post_data("/duplicate_class", {"id": Class["id"], "name": "class2"}) def test_join_class(self): # GIVEN a teacher self.given_teacher_is_logged_in() # GIVEN a class - self.post_data('class', {'name': 'class1'}) - Class = self.get_data('classes')[0] + self.post_data("class", {"name": "class1"}) + Class = self.get_data("classes")[0] # WHEN attempting to join a class without being logged in # THEN receive a forbidden status code from the server - self.post_data('class/join', {'id': Class['id']}, no_cookie=True, expect_http_code=403) + self.post_data( + "class/join", {"id": Class["id"]}, no_cookie=True, expect_http_code=403 + ) # GIVEN a student (user without teacher permissions) self.given_fresh_user_is_logged_in() # WHEN retrieving the short link of a class # THEN receive a redirect to `class/ID/join/LINK` - body = self.get_data('hedy/l/' + Class['link'], expect_http_code=302) - if not re.search(HOST + 'class/' + Class['id'] + '/prejoin/' + Class['link'], body): - raise Exception('Invalid or missing redirect link') + body = self.get_data("hedy/l/" + Class["link"], expect_http_code=302) + if not re.search( + HOST + "class/" + Class["id"] + "/prejoin/" + Class["link"], body + ): + raise Exception("Invalid or missing redirect link") # WHEN joining a class # THEN we receive a 200 code - body = self.post_data('class/join', {'id': Class['id']}, expect_http_code=200) + body = self.post_data("class/join", {"id": Class["id"]}, expect_http_code=200) # WHEN getting own profile after joining a class - profile = self.get_data('profile') + profile = self.get_data("profile") # THEN verify that the class is there and contains the right fields - self.assertIsInstance(profile['student_classes'], list) - self.assertEqual(len(profile['student_classes']), 1) - student_class = profile['student_classes'][0] + self.assertIsInstance(profile["student_classes"], list) + self.assertEqual(len(profile["student_classes"]), 1) + student_class = profile["student_classes"][0] self.assertIsInstance(student_class, dict) - self.assertEqual(student_class['id'], Class['id']) - self.assertEqual(student_class['name'], Class['name']) + self.assertEqual(student_class["id"], Class["id"]) + self.assertEqual(student_class["name"], Class["name"]) def test_see_students_in_class(self): # GIVEN a teacher (no classes yet) self.given_fresh_teacher_is_logged_in() teacher = self.user # GIVEN a class - self.post_data('class', {'name': 'class1'}) - Class = self.get_data('classes')[0] + self.post_data("class", {"name": "class1"}) + Class = self.get_data("classes")[0] # GIVEN a student (user without teacher permissions) that has joined the class self.given_fresh_user_is_logged_in() student = self.user - self.post_data('class/join', {'id': Class['id']}, expect_http_code=200) + self.post_data("class/join", {"id": Class["id"]}, expect_http_code=200) # GIVEN the aforementioned teacher self.switch_user(teacher) # WHEN retrieving the class with a student in it - Class_data = self.get_data('for-teachers/class/' + Class['id']) + Class_data = self.get_data("for-teachers/class/" + Class["id"]) # THEN the class should contain a student with valid fields - self.assertEqual(len(Class_data['students']), 1) - class_student = Class_data['students'][0] - self.assertEqual(class_student['highest_level'], "-") - self.assertEqual(class_student['programs'], 0) - self.assertIsInstance(class_student['last_login'], str) - self.assertEqual(class_student['username'], student['username']) + self.assertEqual(len(Class_data["students"]), 1) + class_student = Class_data["students"][0] + self.assertEqual(class_student["highest_level"], "-") + self.assertEqual(class_student["programs"], 0) + self.assertIsInstance(class_student["last_login"], str) + self.assertEqual(class_student["username"], student["username"]) # WHEN retrieving the student's programs # THEN receive an OK response code from the server - self.get_data('programs?user=' + student['username'], expect_http_code=200) + self.get_data("programs?user=" + student["username"], expect_http_code=200) def test_see_students_with_programs_in_class(self): # GIVEN a teacher (no classes yet) self.given_fresh_teacher_is_logged_in() teacher = self.user # GIVEN a class - self.post_data('class', {'name': 'class1'}) - Class = self.get_data('classes')[0] + self.post_data("class", {"name": "class1"}) + Class = self.get_data("classes")[0] # GIVEN a student (user without teacher permissions) that has joined the # class and has a public program self.given_fresh_user_is_logged_in() - self.post_data('class/join', {'id': Class['id']}, expect_http_code=200) + self.post_data("class/join", {"id": Class["id"]}, expect_http_code=200) # GIVEN a student with two programs, one public and one private - public_program = {'code': 'hello world', 'name': 'program 1', 'level': 1, 'shared': False} - public_program_id = self.post_data('programs', public_program)['id'] - self.post_data('programs/share', {'id': public_program_id, 'public': True}) - private_program = {'code': 'hello world', 'name': 'program 2', 'level': 2, 'shared': False} - self.post_data('programs', private_program)['id'] + public_program = { + "code": "hello world", + "name": "program 1", + "level": 1, + "shared": False, + } + public_program_id = self.post_data("programs", public_program)["id"] + self.post_data("programs/share", {"id": public_program_id, "public": True}) + private_program = { + "code": "hello world", + "name": "program 2", + "level": 2, + "shared": False, + } + self.post_data("programs", private_program)["id"] # GIVEN the aforementioned teacher self.switch_user(teacher) # WHEN retrieving the class with a student in it - Class_data = self.get_data('for-teachers/class/' + Class['id']) + Class_data = self.get_data("for-teachers/class/" + Class["id"]) # THEN the class should contain a student with valid fields - self.assertEqual(len(Class_data['students']), 1) + self.assertEqual(len(Class_data["students"]), 1) class TestCustomizeClasses(AuthHelper): @@ -1400,7 +1587,9 @@ def test_not_allowed_customization(self): # WHEN customizing a class without being a teacher # THEN receive a forbidden response code from the server - self.post_data('for-teachers/customize-class/' + class_id, {}, expect_http_code=403) + self.post_data( + "for-teachers/customize-class/" + class_id, {}, expect_http_code=403 + ) def test_invalid_customization(self): # GIVEN a user with teacher permissions @@ -1410,32 +1599,34 @@ def test_invalid_customization(self): # WHEN creating a class # THEN receive an OK response code with the server # AND retrieve the class_id from the first class of your classes - self.post_data('class', {'name': 'class1'}) - class_id = self.get_data('classes')[0].get('id') - self.get_data('for-teachers/customize-class/' + class_id) + self.post_data("class", {"name": "class1"}) + class_id = self.get_data("classes")[0].get("id") + self.get_data("for-teachers/customize-class/" + class_id) # WHEN attempting to create an invalid customization invalid_bodies = [ - '', + "", [], {}, - {'levels': 1}, - {'levels': [1, 2, 3]}, - {'levels': [1, 2, 3], 'other_settings': {}}, - {'levels': [1, 2, 3], 'opening_dates': []}, - {'opening_dates': {}}, + {"levels": 1}, + {"levels": [1, 2, 3]}, + {"levels": [1, 2, 3], "other_settings": {}}, + {"levels": [1, 2, 3], "opening_dates": []}, + {"opening_dates": {}}, ] for invalid_body in invalid_bodies: # THEN receive an invalid response code from the server self.post_data( - 'for-teachers/customize-class/' + - class_id, + "for-teachers/customize-class/" + class_id, invalid_body, - expect_http_code=400) + expect_http_code=400, + ) # WHEN customizing a class that doesn't exist # THEN receive a not found response code from the server - self.post_data('for-teachers/customize-class/123' + class_id, {}, expect_http_code=404) + self.post_data( + "for-teachers/customize-class/123" + class_id, {}, expect_http_code=404 + ) def test_valid_customization(self): # GIVEN a user with teacher permissions @@ -1445,44 +1636,44 @@ def test_valid_customization(self): # WHEN creating a class # THEN receive an OK response code with the server # AND retrieve the class_id from the first class of your classes - self.post_data('class', {'name': 'class1'}) - class_id = self.get_data('classes')[0].get('id') - self.get_data('for-teachers/customize-class/' + class_id) + self.post_data("class", {"name": "class1"}) + class_id = self.get_data("classes")[0].get("id") + self.get_data("for-teachers/customize-class/" + class_id) valid_bodies = [ { - 'levels': [], - 'opening_dates': {}, - 'other_settings': [], - 'level_thresholds': {} + "levels": [], + "opening_dates": {}, + "other_settings": [], + "level_thresholds": {}, }, { - 'levels': ['1'], - 'opening_dates': {'1': '2022-03-16'}, - 'other_settings': [], - 'level_thresholds': {} + "levels": ["1"], + "opening_dates": {"1": "2022-03-16"}, + "other_settings": [], + "level_thresholds": {}, }, { - 'levels': ['1', '2', '3'], - 'opening_dates': {'1': '', '2': '', '3': ''}, - 'other_settings': [], - 'level_thresholds': {} + "levels": ["1", "2", "3"], + "opening_dates": {"1": "", "2": "", "3": ""}, + "other_settings": [], + "level_thresholds": {}, }, { - 'levels': ['1', '2', '3'], - 'opening_dates': {'1': '', '2': '', '3': ''}, - 'teacher_adventures': [], - 'other_settings': ['developers_mode', 'hide_cheatsheet'], - 'level_thresholds': {} + "levels": ["1", "2", "3"], + "opening_dates": {"1": "", "2": "", "3": ""}, + "teacher_adventures": [], + "other_settings": ["developers_mode", "hide_cheatsheet"], + "level_thresholds": {}, }, ] for valid_body in valid_bodies: # THEN receive an invalid response code from the server self.post_data( - 'for-teachers/customize-class/' + - class_id, + "for-teachers/customize-class/" + class_id, valid_body, - expect_http_code=200) + expect_http_code=200, + ) def test_remove_customization(self): # GIVEN a user with teacher permissions @@ -1492,15 +1683,17 @@ def test_remove_customization(self): # WHEN creating a class # THEN receive an OK response code with the server # AND retrieve the class_id from the first class of your classes - self.post_data('class', {'name': 'class1'}) - class_id = self.get_data('classes')[0].get('id') + self.post_data("class", {"name": "class1"}) + class_id = self.get_data("classes")[0].get("id") # WHEN creating class customizations # THEN receive an OK response code with the server - self.get_data('for-teachers/customize-class/' + class_id, expect_http_code=200) + self.get_data("for-teachers/customize-class/" + class_id, expect_http_code=200) # WHEN deleting class customizations # THEN receive an OK response code with the server - self.post_data('for-teachers/restore-customizations?level=1', {}, expect_http_code=200) + self.post_data( + "for-teachers/restore-customizations?level=1", {}, expect_http_code=200 + ) class TestCustomAdventures(AuthHelper): @@ -1510,29 +1703,32 @@ def test_not_allowed_create_adventure(self): # WHEN trying to create a custom adventure # THEN receive a forbidden response code from the server - self.post_data('for-teachers/create_adventure', {}, expect_http_code=403) + self.post_data("for-teachers/create_adventure", {}, expect_http_code=403) def test_invalid_create_adventure(self): # GIVEN a new teacher self.given_fresh_teacher_is_logged_in() # WHEN attempting to create an invalid adventure - invalid_bodies = [ - '', - [], - {}, - {'name': 123} - ] + invalid_bodies = ["", [], {}, {"name": 123}] for invalid_body in invalid_bodies: - self.post_data('for-teachers/create_adventure', invalid_body, expect_http_code=400) + self.post_data( + "for-teachers/create_adventure", invalid_body, expect_http_code=400 + ) # WHEN attempting to create an adventure that already exists # THEN receive an 400 error from the server - self.post_data('for-teachers/create_adventure', - {'name': 'test_adventure'}, expect_http_code=200) - self.post_data('for-teachers/create_adventure', - {'name': 'test_adventure'}, expect_http_code=400) + self.post_data( + "for-teachers/create_adventure", + {"name": "test_adventure"}, + expect_http_code=200, + ) + self.post_data( + "for-teachers/create_adventure", + {"name": "test_adventure"}, + expect_http_code=400, + ) def test_create_adventure(self): # GIVEN a new teacher @@ -1540,8 +1736,11 @@ def test_create_adventure(self): # WHEN attempting to create a valid adventure # THEN receive an OK response with the server - self.post_data('for-teachers/create_adventure', - {'name': 'test_adventure'}, expect_http_code=200) + self.post_data( + "for-teachers/create_adventure", + {"name": "test_adventure"}, + expect_http_code=200, + ) def test_invalid_view_adventure(self): # GIVEN a new user @@ -1549,14 +1748,14 @@ def test_invalid_view_adventure(self): # WHEN attempting to view a custom adventure # THEN receive a 403 error from the server - self.get_data('for-teachers/customize-adventure/view/123', expect_http_code=403) + self.get_data("for-teachers/customize-adventure/view/123", expect_http_code=403) # GIVEN a new teacher self.given_fresh_teacher_is_logged_in() # WHEN attempting to view a custom adventure that doesn't exist # THEN receive a 404 error from the server - self.get_data('for-teachers/customize-adventure/view/123', expect_http_code=404) + self.get_data("for-teachers/customize-adventure/view/123", expect_http_code=404) def test_valid_view_adventure(self): # GIVEN a new teacher @@ -1564,12 +1763,15 @@ def test_valid_view_adventure(self): # WHEN attempting to create a valid adventure # THEN receive an OK response with the server - adventure_id = self.post_data('for-teachers/create_adventure', - {'name': 'test_adventure'}, expect_http_code=200).get("id") + adventure_id = self.post_data( + "for-teachers/create_adventure", + {"name": "test_adventure"}, + expect_http_code=200, + ).get("id") # WHEN attempting to view the adventure using the id from the returned body # THEN receive an OK response with the server - self.get_data('for-teachers/customize-adventure/view/' + adventure_id) + self.get_data("for-teachers/customize-adventure/view/" + adventure_id) def test_invalid_update_adventure(self): # GIVEN a new teacher @@ -1577,74 +1779,57 @@ def test_invalid_update_adventure(self): # WHEN attempting to create a valid adventure # THEN receive an OK response with the server - adventure_id = self.post_data('for-teachers/create_adventure', - {'name': 'test_adventure'}, expect_http_code=200).get("id") + adventure_id = self.post_data( + "for-teachers/create_adventure", + {"name": "test_adventure"}, + expect_http_code=200, + ).get("id") # WHEN attempting to updating an adventure with invalid data invalid_bodies = [ - '', + "", [], {}, + {"id": 123}, + {"id": 123, "name": 123}, + {"id": "123", "name": 123}, + {"id": "123", "name": 123, "classes": []}, + {"id": "123", "name": 123, "classes": [], "level": 5}, + {"id": "123", "name": 123, "classes": [], "level": 5, "content": 123}, { - 'id': 123 - }, - { - 'id': 123, - 'name': 123 - }, - { - 'id': '123', - 'name': 123 - }, - { - 'id': '123', - 'name': 123, - 'classes': [] - }, - { - 'id': '123', - 'name': 123, - 'classes': [], - 'level': 5 + "id": adventure_id, + "name": "panda", + "classes": [], + "level": "5", + "content": "too short!", }, { - 'id': '123', - 'name': 123, - 'classes': [], - 'level': 5, - 'content': 123 - }, - { - 'id': adventure_id, - 'name': 'panda', - 'classes': [], - 'level': '5', - 'content': 'too short!' - }, - { - 'id': adventure_id, - 'name': 'panda', - 'classes': [], - 'level': '5', - 'content': 'This is just long enough!', - 'public': 'panda' + "id": adventure_id, + "name": "panda", + "classes": [], + "level": "5", + "content": "This is just long enough!", + "public": "panda", }, ] # THEN receive a 400 error from the server for invalid_body in invalid_bodies: - self.post_data('for-teachers/customize-adventure', invalid_body, expect_http_code=400) + self.post_data( + "for-teachers/customize-adventure", invalid_body, expect_http_code=400 + ) # WHEN attempting to update a non-existing adventure # THEN receive a 404 error from the server body = { - 'id': '123', - 'name': 'panda', - 'classes': [], - 'level': '5', - 'content': 'This is just long enough!', - 'public': True} - self.post_data('for-teachers/customize-adventure', body, expect_http_code=404) + "id": "123", + "name": "panda", + "classes": [], + "level": "5", + "content": "This is just long enough!", + "public": True, + } + self.post_data("for-teachers/customize-adventure", body, expect_http_code=404) def test_valid_update_adventure(self): # GIVEN a new teacher @@ -1652,19 +1837,23 @@ def test_valid_update_adventure(self): # WHEN attempting to create a valid adventure # THEN receive an OK response from the server - adventure_id = self.post_data('for-teachers/create_adventure', - {'name': 'test_adventure'}, expect_http_code=200).get("id") + adventure_id = self.post_data( + "for-teachers/create_adventure", + {"name": "test_adventure"}, + expect_http_code=200, + ).get("id") # WHEN attempting to update an adventure with a valid body # THEN receive an OK response from the server body = { - 'id': adventure_id, - 'name': 'test_adventure', - 'classes': [], - 'level': '5', - 'content': 'This is just long enough!', - 'public': True} - self.post_data('for-teachers/customize-adventure', body, expect_http_code=200) + "id": adventure_id, + "name": "test_adventure", + "classes": [], + "level": "5", + "content": "This is just long enough!", + "public": True, + } + self.post_data("for-teachers/customize-adventure", body, expect_http_code=200) def test_valid_update_adventure_with_class(self): # GIVEN a new teacher @@ -1672,26 +1861,29 @@ def test_valid_update_adventure_with_class(self): # WHEN attempting to create a valid adventure # THEN receive an OK response from the server - adventure_id = self.post_data('for-teachers/create_adventure', - {'name': 'test_adventure'}, expect_http_code=200).get("id") + adventure_id = self.post_data( + "for-teachers/create_adventure", + {"name": "test_adventure"}, + expect_http_code=200, + ).get("id") # WHEN attempting to create a valid adventure # THEN receive an OK response from the server AND retrieve the class_id - self.post_data('class', {'name': 'class1'}) - class_id = self.get_data('classes')[0].get('id') + self.post_data("class", {"name": "class1"}) + class_id = self.get_data("classes")[0].get("id") # WHEN attempting to update an adventure with a valid body # THEN receive an OK response from the server body = { - 'id': adventure_id, - 'name': 'test_adventure', - 'classes': [class_id], - 'level': '5', - 'content': 'This is just long enough!', - 'public': True + "id": adventure_id, + "name": "test_adventure", + "classes": [class_id], + "level": "5", + "content": "This is just long enough!", + "public": True, } - self.post_data('for-teachers/customize-adventure', body, expect_http_code=200) + self.post_data("for-teachers/customize-adventure", body, expect_http_code=200) def test_destroy_adventure(self): # GIVEN a user with teacher permissions @@ -1700,13 +1892,18 @@ def test_destroy_adventure(self): # WHEN attempting to create a valid adventure # THEN receive an OK response from the server - body = self.post_data('for-teachers/create_adventure', - {'name': 'test_adventure'}, expect_http_code=200) + body = self.post_data( + "for-teachers/create_adventure", + {"name": "test_adventure"}, + expect_http_code=200, + ) # WHEN attempting to remove the adventure # THEN receive an OK response from the server - self.delete_data('for-teachers/customize-adventure/' + - body.get('id', ""), expect_http_code=200) + self.delete_data( + "for-teachers/customize-adventure/" + body.get("id", ""), + expect_http_code=200, + ) class TestMultipleAccounts(AuthHelper): @@ -1716,7 +1913,7 @@ def test_not_allowed_create_accounts(self): # WHEN trying to create multiple accounts # THEN receive a forbidden response code from the server - self.post_data('for-teachers/create-accounts', {}, expect_http_code=403) + self.post_data("for-teachers/create-accounts", {}, expect_http_code=403) def test_invalid_create_accounts(self): # GIVEN a new teacher @@ -1724,18 +1921,25 @@ def test_invalid_create_accounts(self): # WHEN attempting to create invalid accounts invalid_bodies = [ - '', + "", [], {}, - {'accounts': []}, - {'accounts': [{'username': 123}]}, - {'accounts': [{'username': 'panda', 'password': 123}]}, - {'accounts': [{'username': '@', 'password': 'test123'}]}, - {'accounts': [{'username': 'panda', 'password': 'test123'}, {'username': 'panda2', 'password': 123}]} + {"accounts": []}, + {"accounts": [{"username": 123}]}, + {"accounts": [{"username": "panda", "password": 123}]}, + {"accounts": [{"username": "@", "password": "test123"}]}, + { + "accounts": [ + {"username": "panda", "password": "test123"}, + {"username": "panda2", "password": 123}, + ] + }, ] for invalid_body in invalid_bodies: - self.post_data('for-teachers/create-accounts', invalid_body, expect_http_code=400) + self.post_data( + "for-teachers/create-accounts", invalid_body, expect_http_code=400 + ) def test_create_accounts(self): # GIVEN a new teacher @@ -1744,12 +1948,13 @@ def test_create_accounts(self): # WHEN attempting to create a valid adventure # THEN receive an OK response with the server body = { - 'accounts': [ - {'username': 'panda', 'password': 'test123'}, - {'username': 'panda2', 'password': 'test321'} + "accounts": [ + {"username": "panda", "password": "test123"}, + {"username": "panda2", "password": "test321"}, ] } - self.post_data('for-teachers/create-accounts', body, expect_http_code=200) + self.post_data("for-teachers/create-accounts", body, expect_http_code=200) + # *** CLEANUP OF USERS CREATED DURING THE TESTS *** diff --git a/tools/check-yaml-structure.py b/tools/check-yaml-structure.py index ff210e9bac5..4c9ba555562 100644 --- a/tools/check-yaml-structure.py +++ b/tools/check-yaml-structure.py @@ -11,48 +11,59 @@ def main(): any_failure = False - for reference_file in glob.glob('content/*/en.yaml'): + for reference_file in glob.glob("content/*/en.yaml"): en = load_yaml(reference_file) structure_dir = path.basename(path.dirname(reference_file)) - mismatches = {comparison_file: find_mismatched_arrays(en, load_yaml(comparison_file)) - for comparison_file in glob.glob(f'content/{structure_dir}/*.yaml') - if comparison_file != reference_file} + mismatches = { + comparison_file: find_mismatched_arrays(en, load_yaml(comparison_file)) + for comparison_file in glob.glob(f"content/{structure_dir}/*.yaml") + if comparison_file != reference_file + } mismatches = {file: mis for file, mis in mismatches.items() if mis} if mismatches: any_failure = True - print(f'==================== {path.dirname(reference_file)} =======================') - print(' Different array lengths between English and other languages.') - print(' Please make the arrays the same by copying the new English content') - print(' to the right places in the other files.') + print( + f"==================== {path.dirname(reference_file)} =======================" + ) + print(" Different array lengths between English and other languages.") + print( + " Please make the arrays the same by copying the new English content" + ) + print(" to the right places in the other files.") print() # If there are many mismatches, the most natural way to present this information # is { path -> file -> mismatch }, but we have { file -> path -> mismatch }, so # we have to transpose. - unique_paths = list(sorted(set(p for paths in mismatches.values() for p in paths))) + unique_paths = list( + sorted(set(p for paths in mismatches.values() for p in paths)) + ) for p in unique_paths: - mis_by_file = {lang_file: mis_by_path[p] - for lang_file, mis_by_path in mismatches.items() if mis_by_path.get(p)} + mis_by_file = { + lang_file: mis_by_path[p] + for lang_file, mis_by_path in mismatches.items() + if mis_by_path.get(p) + } first_mis = mis_by_file[next(iter(mis_by_file.keys()))] - print(f'---------------[ Path in YAML: {p} ]---------------------') - print(f'File: {reference_file} ({len(first_mis.left)} elements)') - print('') + print(f"---------------[ Path in YAML: {p} ]---------------------") + print(f"File: {reference_file} ({len(first_mis.left)} elements)") + print("") print(indent(4, yaml_to_string(shortened(first_mis.left)))) for file, mis in mis_by_file.items(): - print(f'File: {file} ({len(mis.right)} elements)') - print('') + print(f"File: {file} ({len(mis.right)} elements)") + print("") print(indent(4, yaml_to_string(shortened(mis.right)))) - print('') + print("") return 1 if any_failure else 0 -Mismatch = collections.namedtuple('Mismatch', ('left', 'right')) +Mismatch = collections.namedtuple("Mismatch", ("left", "right")) def find_mismatched_arrays(reference, other): @@ -67,15 +78,15 @@ def find_mismatched_arrays(reference, other): def recurse(ref, oth, p): if isinstance(ref, dict) and isinstance(oth, dict): for key in set(ref.keys()) & set(oth.keys()): - recurse(ref[key], oth[key], p + [f'.{key}']) + recurse(ref[key], oth[key], p + [f".{key}"]) return if isinstance(ref, list) and isinstance(oth, list): if len(ref) != len(oth): - ret[''.join(p)] = Mismatch(ref, oth) + ret["".join(p)] = Mismatch(ref, oth) else: for i in range(min(len(ref), len(oth))): - recurse(ref[i], oth[i], p + [f'[{i}]']) + recurse(ref[i], oth[i], p + [f"[{i}]"]) return recurse(reference, other, []) @@ -87,20 +98,20 @@ def shortened(obj, depth=2): as well as stopping recursion after a certain limit. """ if isinstance(obj, str): - return obj if len(obj) < 60 else obj[:60] + '{...}' + return obj if len(obj) < 60 else obj[:60] + "{...}" if isinstance(obj, dict): if depth == 0: - return '{ ' + ', '.join(sorted(obj.keys())) + ' }' - return {k: shortened(v, depth-1) for k, v in obj.items()} + return "{ " + ", ".join(sorted(obj.keys())) + " }" + return {k: shortened(v, depth - 1) for k, v in obj.items()} if isinstance(obj, list): if depth == 0: - return f'[ ...{len(obj)} elements... ]' - return [shortened(x, depth-1) for x in obj] + return f"[ ...{len(obj)} elements... ]" + return [shortened(x, depth - 1) for x in obj] return obj def load_yaml(filename): - with open(filename, 'r') as f: + with open(filename, "r") as f: return yaml_writer.load(f) @@ -111,9 +122,9 @@ def yaml_to_string(x): def indent(n, x): - prefix = ' ' * n - return '\n'.join(prefix + ln for ln in x.split('\n')) + prefix = " " * n + return "\n".join(prefix + ln for ln in x.split("\n")) -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/tools/download-database.py b/tools/download-database.py index 9808f2d9dd8..af197377c7f 100644 --- a/tools/download-database.py +++ b/tools/download-database.py @@ -15,39 +15,38 @@ def main(): - - REGION = 'eu-west-1' + REGION = "eu-west-1" defs = TableDefinitions() - defs.add('hedy-beta-achievements') - defs.add('hedy-beta-adventures') - defs.add('hedy-beta-classes') - defs.add('hedy-beta-class_customizations') - defs.add('hedy-beta-class_invitations') - defs.add('hedy-beta-parsons') - defs.add('hedy-beta-program-stats') - defs.add('hedy-beta-programs') - defs.add('hedy-beta-public_profiles') - defs.add('hedy-beta-quiz-stats') - defs.add('hedy-beta-quizAnswers') - defs.add('hedy-beta-tokens') - defs.add('hedy-beta-users') - - parser = argparse.ArgumentParser(description='Download DDB into SQLite') + defs.add("hedy-beta-achievements") + defs.add("hedy-beta-adventures") + defs.add("hedy-beta-classes") + defs.add("hedy-beta-class_customizations") + defs.add("hedy-beta-class_invitations") + defs.add("hedy-beta-parsons") + defs.add("hedy-beta-program-stats") + defs.add("hedy-beta-programs") + defs.add("hedy-beta-public_profiles") + defs.add("hedy-beta-quiz-stats") + defs.add("hedy-beta-quizAnswers") + defs.add("hedy-beta-tokens") + defs.add("hedy-beta-users") + + parser = argparse.ArgumentParser(description="Download DDB into SQLite") subparsers = parser.add_subparsers() - cmd_download = subparsers.add_parser('download') - cmd_download.set_defaults(command='download') + cmd_download = subparsers.add_parser("download") + cmd_download.set_defaults(command="download") - cmd_insert = subparsers.add_parser('insert') - cmd_insert.set_defaults(command='insert') + cmd_insert = subparsers.add_parser("insert") + cmd_insert.set_defaults(command="insert") args = parser.parse_args() - if 'command' not in args or args.command == 'download': - ddb = boto3.client('dynamodb', region_name=REGION) - dl = TableDownload(ddb, 'download') + if "command" not in args or args.command == "download": + ddb = boto3.client("dynamodb", region_name=REGION) + dl = TableDownload(ddb, "download") dl.download_all(defs) - if 'command' not in args or args.command == 'insert': - inserter = TableInserter('db.sqlite3', 'download') + if "command" not in args or args.command == "insert": + inserter = TableInserter("db.sqlite3", "download") inserter.insert_all(defs) @@ -56,23 +55,25 @@ def __init__(self, dbfile, jsondirectory): self.db = sqlite3.connect(dbfile) self.jsondirectory = jsondirectory - def insert_all(self, defs: 'TableDefinitions'): + def insert_all(self, defs: "TableDefinitions"): for table_name in defs.table_names: self.insert_table(table_name) def insert_table(self, table_name): print(table_name) - with open(path.join(self.jsondirectory, f'{table_name}.json'), 'r', encoding='utf-8') as f: + with open( + path.join(self.jsondirectory, f"{table_name}.json"), "r", encoding="utf-8" + ) as f: table_data = json.load(f) - if not table_data['rows']: + if not table_data["rows"]: return - table_data['rows'] = restore_types(table_data['rows']) + table_data["rows"] = restore_types(table_data["rows"]) - columns = determine_columns(table_data['rows']) + columns = determine_columns(table_data["rows"]) scalar_columns = [col for col in columns if not col.type.is_collection] - keycolumns = [find_col(scalar_columns, k) for k in table_data['key']] + keycolumns = [find_col(scalar_columns, k) for k in table_data["key"]] table = SqlTableDef(table_name, scalar_columns, keycolumns) @@ -81,36 +82,45 @@ def insert_table(self, table_name): cursor.execute(table.create_statement) cursor.executemany( - table.insert_statement, - table.extract_table_values(table_data['rows'])) + table.insert_statement, table.extract_table_values(table_data["rows"]) + ) # Lists for listcol in (col for col in columns if col.type.is_list or col.type.is_set): - typ = SqlType.most_generic(value for row in table_data['rows'] - for value in row.get(listcol.original_name, [])) + typ = SqlType.most_generic( + value + for row in table_data["rows"] + for value in row.get(listcol.original_name, []) + ) onetomanycol = SqlColumn(listcol.original_name, typ) onetomanytable = SqlTableDef( - f'{table.table_name}_{onetomanycol.name}', + f"{table.table_name}_{onetomanycol.name}", table.key_columns + [onetomanycol], - table.key_columns + [onetomanycol] if listcol.type.is_set else []) + table.key_columns + [onetomanycol] if listcol.type.is_set else [], + ) print(onetomanytable.table_name) cursor.execute(onetomanytable.drop_statement) cursor.execute(onetomanytable.create_statement) one_to_many_data = [] - for row in table_data['rows']: + for row in table_data["rows"]: for listvalue in row.get(listcol.original_name, []): - one_to_many_data.append(tuple( - [row.get(key_col.original_name) for key_col in table.key_columns] - + - [listvalue])) + one_to_many_data.append( + tuple( + [ + row.get(key_col.original_name) + for key_col in table.key_columns + ] + + [listvalue] + ) + ) cursor.executemany(onetomanytable.insert_statement, one_to_many_data) # Maps (not implemented yet) for mapcol in (col for col in columns if col.type.is_map): - print(f'Dropping column: {table.original_name}.{mapcol.original_name}') + print(f"Dropping column: {table.original_name}.{mapcol.original_name}") self.db.commit() @@ -118,19 +128,19 @@ def insert_table(self, table_name): def find_col(cols, name): cs = [col for col in cols if col.original_name == name] if not cs: - raise RuntimeError(f'Could not find col {name}') + raise RuntimeError(f"Could not find col {name}") return cs[0] class SqlColumn: - def __init__(self, name, type: 'SqlType'): + def __init__(self, name, type: "SqlType"): self.original_name = name self.name = slugify(name) self.type = type self.sql_def = f'"{self.name}" {self.type.sql_def}' - def widen_type(self, type: 'SqlType'): + def widen_type(self, type: "SqlType"): self.type = self.type.unify(type) @@ -147,21 +157,27 @@ def drop_statement(self): @property def create_statement(self): - table_def = ', '.join( + table_def = ", ".join( [col.sql_def for col in self.columns] - + - (['PRIMARY KEY(' + ', '.join(c.name for c in self.key_columns) + ')'] if self.key_columns else []) + + ( + ["PRIMARY KEY(" + ", ".join(c.name for c in self.key_columns) + ")"] + if self.key_columns + else [] + ) ) return f'CREATE TABLE "{self.table_name}"({table_def});' @property def insert_statement(self): - qmarks = ['?'] * len(self.columns) + qmarks = ["?"] * len(self.columns) return f'INSERT INTO "{self.table_name}" VALUES({", ".join(qmarks)});' def extract_values(self, row): - return tuple(row[c.original_name] if c.original_name in row else None for c in self.columns) + return tuple( + row[c.original_name] if c.original_name in row else None + for c in self.columns + ) def extract_table_values(self, table): return [self.extract_values(row) for row in table] @@ -184,31 +200,31 @@ def make_col_def(name, typ): def slugify(x): - return re.sub('[^a-zA-Z0-9]', '_', x) + return re.sub("[^a-zA-Z0-9]", "_", x) class SqlType: @staticmethod def of(value): if isinstance(value, str): - return SqlType('TEXT') + return SqlType("TEXT") if isinstance(value, int): - return SqlType('INTEGER') + return SqlType("INTEGER") if isinstance(value, float): - return SqlType('REAL') + return SqlType("REAL") if isinstance(value, set): - return SqlType('+SET') + return SqlType("+SET") if isinstance(value, list): - return SqlType('+LIST') + return SqlType("+LIST") if isinstance(value, dict): - return SqlType('+MAP') + return SqlType("+MAP") if value is None: - return SqlType('NULL') - raise RuntimeError(f'Do not know type of value {value}') + return SqlType("NULL") + raise RuntimeError(f"Do not know type of value {value}") @staticmethod def null(): - return SqlType('NULL') + return SqlType("NULL") @staticmethod def most_generic(xs): @@ -219,28 +235,28 @@ def most_generic(xs): def __init__(self, type): self.type = type - self.is_collection = type.startswith('+') - self.is_set = type == '+SET' - self.is_list = type == '+LIST' - self.is_map = type == '+MAP' + self.is_collection = type.startswith("+") + self.is_set = type == "+SET" + self.is_list = type == "+LIST" + self.is_map = type == "+MAP" self.sql_def = type def unify(self, rhs): if self.type == rhs.type: return self - if self.type == 'NULL': + if self.type == "NULL": return rhs - if rhs.type == 'NULL': + if rhs.type == "NULL": return self types = [self.type, rhs.type] types.sort() - if types == ['INTEGER', 'REAL']: - return SqlType('REAL') - if types == ['INTEGER', 'TEXT'] or types == ['REAL', 'TEXT']: - return SqlType('TEXT') - raise RuntimeError(f'Cannot unify types {self.type} and {rhs.type}') + if types == ["INTEGER", "REAL"]: + return SqlType("REAL") + if types == ["INTEGER", "TEXT"] or types == ["REAL", "TEXT"]: + return SqlType("TEXT") + raise RuntimeError(f"Cannot unify types {self.type} and {rhs.type}") class TableDefinitions: @@ -265,21 +281,39 @@ def download_table(self, table_name): print(table_name) description = self.ddb.describe_table(TableName=table_name) - partition_key = [k['AttributeName'] for k in description['Table']['KeySchema'] if k['KeyType'] == 'HASH'] - sort_key = [k['AttributeName'] for k in description['Table']['KeySchema'] if k['KeyType'] == 'RANGE'] + partition_key = [ + k["AttributeName"] + for k in description["Table"]["KeySchema"] + if k["KeyType"] == "HASH" + ] + sort_key = [ + k["AttributeName"] + for k in description["Table"]["KeySchema"] + if k["KeyType"] == "RANGE" + ] key = [partition_key[0]] + ([sort_key[0]] if sort_key else []) - columns = {a['AttributeName']: a['AttributeType'] for a in description['Table']['AttributeDefinitions']} + columns = { + a["AttributeName"]: a["AttributeType"] + for a in description["Table"]["AttributeDefinitions"] + } rows = [] - with tqdm(total=description['Table']['ItemCount']) as progressbar: - for page in self.ddb.get_paginator('scan').paginate(TableName=table_name): - for row in page['Items']: - rows.append({key: DDB_DESERIALIZER.deserialize(value) for key, value in row.items()}) + with tqdm(total=description["Table"]["ItemCount"]) as progressbar: + for page in self.ddb.get_paginator("scan").paginate(TableName=table_name): + for row in page["Items"]: + rows.append( + { + key: DDB_DESERIALIZER.deserialize(value) + for key, value in row.items() + } + ) progressbar.update(1) - with open(path.join(self.directory, f'{table_name}.json'), 'w', encoding='utf-8') as f: + with open( + path.join(self.directory, f"{table_name}.json"), "w", encoding="utf-8" + ) as f: json.dump(dict(key=key, rows=rows, columns=columns), f, cls=DDBTypesEncoder) @@ -292,18 +326,20 @@ def default(self, o): return int(o) return float(o) if isinstance(o, set): - return {'@type': 'set', 'set': list(o)} + return {"@type": "set", "set": list(o)} return super(DDBTypesEncoder, self).default(o) def restore_types(rows): """Decode all values that were encoded using DDBTypesEncoder.""" + def decode(o): - if isinstance(o, dict) and o.get('@type') == 'set': - return set(o['set']) + if isinstance(o, dict) and o.get("@type") == "set": + return set(o["set"]) return o + return [{key: decode(value) for key, value in row.items()} for row in rows] -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/tools/logs-to-db.py b/tools/logs-to-db.py index b10b43ee7e5..33d0a048278 100644 --- a/tools/logs-to-db.py +++ b/tools/logs-to-db.py @@ -2,29 +2,48 @@ import sqlite3 import os -connection = sqlite3.connect('db.sqlite3') +connection = sqlite3.connect("db.sqlite3") cursor = connection.cursor() -columns = ['session', 'date', 'lang', 'level', 'code', 'server_error', - 'exception', 'version', 'username', 'adventurename', 'read_aloud'] -value_string = ','.join('?' * len(columns)) - -cursor.execute('DROP TABLE if exists Logs;') -cursor.execute('Create Table Logs ' - '(session Text, ' - 'date datetime, ' - 'lang Text, ' - 'level TinyINT, ' - 'code LONGTEXT, ' - 'server_error Text, ' - 'exception Text,' - 'version Text,' - 'username Text,' - 'adventurename Text,' - 'read_aloud Bool' - ')') - -defaults = {'lang': 'en', 'server_error': None, 'exception': None, - 'username': None, 'adventurename': None, 'read_aloud': None} +columns = [ + "session", + "date", + "lang", + "level", + "code", + "server_error", + "exception", + "version", + "username", + "adventurename", + "read_aloud", +] +value_string = ",".join("?" * len(columns)) + +cursor.execute("DROP TABLE if exists Logs;") +cursor.execute( + "Create Table Logs " + "(session Text, " + "date datetime, " + "lang Text, " + "level TinyINT, " + "code LONGTEXT, " + "server_error Text, " + "exception Text," + "version Text," + "username Text," + "adventurename Text," + "read_aloud Bool" + ")" +) + +defaults = { + "lang": "en", + "server_error": None, + "exception": None, + "username": None, + "adventurename": None, + "read_aloud": None, +} def add_defaults(x): @@ -38,13 +57,13 @@ def add_defaults(x): return keys -directory = 'aws-logs' +directory = "aws-logs" files = [] for root, d_names, f_names in os.walk(directory): for f in f_names: extension = os.path.splitext(f)[1] - if extension == '.jsonl': + if extension == ".jsonl": files.append(os.path.join(root, f)) i = 0 @@ -53,9 +72,9 @@ def add_defaults(x): i += 1 if i % 10000 == 0: - print(f'{round(i / len(files) * 100, 2)}% complete') + print(f"{round(i / len(files) * 100, 2)}% complete") - with open(filename, 'r') as file: + with open(filename, "r") as file: contents = file.readlines() # a file with one or more lines of json for json_line in contents: @@ -67,7 +86,7 @@ def add_defaults(x): keys = tuple(add_defaults(json_dict)) try: - cursor.execute(f'insert into Logs values({value_string})', keys) + cursor.execute(f"insert into Logs values({value_string})", keys) except UnicodeEncodeError: print(f'{json_dict["session"]} data not inserted!!') diff --git a/utils.py b/utils.py index 1afebd96638..2ba66074289 100644 --- a/utils.py +++ b/utils.py @@ -20,19 +20,19 @@ commonmark_parser = commonmark.Parser() commonmark_renderer = commonmark.HtmlRenderer() -IS_WINDOWS = os.name == 'nt' +IS_WINDOWS = os.name == "nt" # Define code that will be used if some turtle command is present -with open('prefixes/turtle.py', encoding='utf-8') as f: +with open("prefixes/turtle.py", encoding="utf-8") as f: TURTLE_PREFIX_CODE = f.read() # Preamble that will be used for non-Turtle programs # numerals list generated from: https://replit.com/@mevrHermans/multilangnumerals -with open('prefixes/normal.py', encoding='utf-8') as f: +with open("prefixes/normal.py", encoding="utf-8") as f: NORMAL_PREFIX_CODE = f.read() # Define code that will be used if a pressed command is used -with open('prefixes/pygame.py', encoding='utf-8') as f: +with open("prefixes/pygame.py", encoding="utf-8") as f: PYGAME_PREFIX_CODE = f.read() @@ -47,15 +47,17 @@ def __enter__(self): def __exit__(self, type, value, tb): delta = time.time() - self.start - print(f'{self.name}: {delta}s') + print(f"{self.name}: {delta}s") def timer(fn): """Decoractor for fn.""" + @functools.wraps(fn) def wrapper(*args, **kwargs): with Timer(fn.__name__): return fn(*args, **kwargs) + return wrapper @@ -97,7 +99,7 @@ def set_debug_mode(debug_mode): def load_yaml_rt(filename): """Load YAML with the round trip loader.""" try: - with open(filename, 'r', encoding='utf-8') as f: + with open(filename, "r", encoding="utf-8") as f: return yaml.round_trip_load(f, preserve_quotes=True) except IOError: return {} @@ -114,10 +116,10 @@ def slash_join(*args): if not arg: continue - if ret and not ret[-1].endswith('/'): - ret.append('/') - ret.append(arg.lstrip('/') if ret else arg) - return ''.join(ret) + if ret and not ret[-1].endswith("/"): + ret.append("/") + ret.append(arg.lstrip("/") if ret else arg) + return "".join(ret) def is_testing_request(request): @@ -128,22 +130,24 @@ def is_testing_request(request): Test requests are only allowed on non-Heroku instances. """ - return not is_heroku() and bool('X-Testing' in request.headers and request.headers['X-Testing']) + return not is_heroku() and bool( + "X-Testing" in request.headers and request.headers["X-Testing"] + ) def extract_bcrypt_rounds(hash): - return int(re.match(r'\$2b\$\d+', hash)[0].replace('$2b$', '')) + return int(re.match(r"\$2b\$\d+", hash)[0].replace("$2b$", "")) def isoformat(timestamp): """Turn a timestamp into an ISO formatted string.""" dt = datetime.datetime.utcfromtimestamp(timestamp) - return dt.isoformat() + 'Z' + return dt.isoformat() + "Z" def is_production(): """Whether we are serving production traffic.""" - return os.getenv('IS_PRODUCTION', '') != '' + return os.getenv("IS_PRODUCTION", "") != "" def is_heroku(): @@ -161,27 +165,29 @@ def is_heroku(): to optimize for developer productivity. """ - return os.getenv('DYNO', '') != '' + return os.getenv("DYNO", "") != "" def version(): # """Get the version from the Heroku environment variables.""" if not is_heroku(): - return 'DEV' + return "DEV" - vrz = os.getenv('HEROKU_RELEASE_CREATED_AT') + vrz = os.getenv("HEROKU_RELEASE_CREATED_AT") the_date = datetime.date.fromisoformat(vrz[:10]) if vrz else datetime.date.today() - commit = os.getenv('HEROKU_SLUG_COMMIT', '????')[0:6] - return the_date.strftime('%Y %b %d') + f'({commit})' + commit = os.getenv("HEROKU_SLUG_COMMIT", "????")[0:6] + return the_date.strftime("%Y %b %d") + f"({commit})" def valid_email(s): - return bool(re.match(r'^(([a-zA-Z0-9_+\.\-]+)@([\da-zA-Z\.\-]+)\.([a-zA-Z\.]{2,6})\s*)$', s)) + return bool( + re.match(r"^(([a-zA-Z0-9_+\.\-]+)@([\da-zA-Z\.\-]+)\.([a-zA-Z\.]{2,6})\s*)$", s) + ) @contextlib.contextmanager -def atomic_write_file(filename, mode='wb'): +def atomic_write_file(filename, mode="wb"): """Write to a filename atomically. First write to a unique tempfile, then rename the tempfile into @@ -192,7 +198,7 @@ def atomic_write_file(filename, mode='wb'): f.write('hello') """ - tmp_file = f'{filename}.{os.getpid()}' + tmp_file = f"{filename}.{os.getpid()}" with open(tmp_file, mode) as f: yield f @@ -222,9 +228,13 @@ def timestamp_to_date(timestamp, short_format=False): def delta_timestamp(date, short_format=False): if short_format: - delta = datetime.datetime.now() - datetime.datetime.fromtimestamp(int(str(date))) + delta = datetime.datetime.now() - datetime.datetime.fromtimestamp( + int(str(date)) + ) else: - delta = datetime.datetime.now() - datetime.datetime.fromtimestamp(int(str(date)[:-3])) + delta = datetime.datetime.now() - datetime.datetime.fromtimestamp( + int(str(date)[:-3]) + ) return format_timedelta(delta) @@ -238,7 +248,11 @@ def localized_date_format(date, short_format=False): timestamp = datetime.datetime.fromtimestamp(int(str(date))) else: timestamp = datetime.datetime.fromtimestamp(int(str(date)[:-3])) - return format_date(timestamp, format='medium') + " " + format_datetime(timestamp, "H:mm") + return ( + format_date(timestamp, format="medium") + + " " + + format_datetime(timestamp, "H:mm") + ) def datetotimeordate(date): @@ -250,11 +264,9 @@ def datetotimeordate(date): def random_id_generator( - size=6, - chars=string.ascii_uppercase + - string.ascii_lowercase + - string.digits): - return ''.join(random.choice(chars) for _ in range(size)) + size=6, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits +): + return "".join(random.choice(chars) for _ in range(size)) # This function takes a Markdown string and returns a list with each of the HTML elements obtained @@ -263,54 +275,81 @@ def random_id_generator( def markdown_to_html_tags(markdown): _html = commonmark_renderer.render(commonmark_parser.parse(markdown)) - soup = BeautifulSoup(_html, 'html.parser') + soup = BeautifulSoup(_html, "html.parser") return soup.find_all() -def error_page(error=404, page_error=None, ui_message=None, menu=True, iframe=None, exception=None): +def error_page( + error=404, page_error=None, ui_message=None, menu=True, iframe=None, exception=None +): if error not in [403, 404, 500]: error = 404 - default = gettext('default_404') + default = gettext("default_404") if error == 403: - default = gettext('default_403') + default = gettext("default_403") elif error == 500: - default = gettext('default_500') + default = gettext("default_500") - hx_request = bool(request.headers.get('Hx-Request')) + hx_request = bool(request.headers.get("Hx-Request")) if hx_request: # For HTMX-request, just return the error as plain text body - return make_response(f'{default} {exception}', error) + return make_response(f"{default} {exception}", error) - if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html: + if ( + request.accept_mimetypes.accept_json + and not request.accept_mimetypes.accept_html + ): # Produce a JSON response instead of an HTML response - return jsonify({"code": error, - "error": default, - "exception": traceback.format_exception(type(exception), exception, exception.__traceback__) if exception else None}), error - - return render_template("error-page.html", menu=menu, error=error, iframe=iframe, - page_error=page_error or ui_message or '', default=default), error + return ( + jsonify( + { + "code": error, + "error": default, + "exception": traceback.format_exception( + type(exception), exception, exception.__traceback__ + ) + if exception + else None, + } + ), + error, + ) + + return ( + render_template( + "error-page.html", + menu=menu, + error=error, + iframe=iframe, + page_error=page_error or ui_message or "", + default=default, + ), + error, + ) def session_id(): """Returns or sets the current session ID.""" - if 'session_id' not in session: - if os.getenv('IS_TEST_ENV') and 'X-session_id' in request.headers: - session['session_id'] = request.headers['X-session_id'] + if "session_id" not in session: + if os.getenv("IS_TEST_ENV") and "X-session_id" in request.headers: + session["session_id"] = request.headers["X-session_id"] else: - session['session_id'] = uuid.uuid4().hex - return session['session_id'] + session["session_id"] = uuid.uuid4().hex + return session["session_id"] # https://github.com/python-babel/babel/issues/454 def customize_babel_locale(custom_locales: dict): from babel.core import get_global - db = get_global('likely_subtags') + + db = get_global("likely_subtags") for custom_name in custom_locales: db[custom_name] = custom_name import babel.localedata o_exists, o_load = babel.localedata.exists, babel.localedata.load if o_exists.__module__ != __name__: + def exists(name): name = custom_locales.get(name, name) return o_exists(name) @@ -324,8 +363,9 @@ def load(name, merge_inherited=True): def strip_accents(s): - return ''.join(c for c in unicodedata.normalize('NFD', s) - if unicodedata.category(c) != 'Mn') + return "".join( + c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn" + ) def base_url(): @@ -333,8 +373,8 @@ def base_url(): Returns either from configuration or otherwise from Flask. """ - url = os.getenv('BASE_URL') + url = os.getenv("BASE_URL") if not url: url = request.host_url - return url if not url.endswith('/') else url[:-1] + return url if not url.endswith("/") else url[:-1] diff --git a/website/ab_proxying.py b/website/ab_proxying.py index bc31563d7d2..d23840022de 100644 --- a/website/ab_proxying.py +++ b/website/ab_proxying.py @@ -35,7 +35,13 @@ def before_request_proxy(self): # /session_test is meant to return the session from the test environment, for testing purposes. elif re.match(".*/session_test", request.url) or redirect_ab(request): url = self.target_host + request.full_path - logging.debug("Proxying %s %s %s to %s", request.method, request.url, dict(session), url) + logging.debug( + "Proxying %s %s %s to %s", + request.method, + request.url, + dict(session), + url, + ) request_headers = {} for header in request.headers: @@ -45,20 +51,29 @@ def before_request_proxy(self): # In case the session_id is not yet set in the cookie, pass it in a special header request_headers["X-session_id"] = session["session_id"] - r = getattr(requests, request.method.lower())(url, headers=request_headers, data=request.data) + r = getattr(requests, request.method.lower())( + url, headers=request_headers, data=request.data + ) response = make_response(r.content) for header in r.headers: # With great help from # https://medium.com/customorchestrator/simple-reverse-proxy-server-using-flask-936087ce0afb - if header.lower() in ["content-encoding", "content-length", "transfer-encoding", "connection"]: + if header.lower() in [ + "content-encoding", + "content-length", + "transfer-encoding", + "connection", + ]: continue # Setting the session cookie returned by the test environment into the response # won't work because it will be overwritten by Flask, # so we need to read the cookie into the session so that then # the session cookie can be updated by Flask if header.lower() == "set-cookie": - proxied_session = extract_session_from_cookie(r.headers[header], self.secret_key) + proxied_session = extract_session_from_cookie( + r.headers[header], self.secret_key + ) for key in proxied_session: session[key] = proxied_session[key] continue @@ -101,7 +116,8 @@ def extract_session_from_cookie(cookie_header, secret_key): salt=cookie_interface.salt, serializer=cookie_interface.serializer, signer_kwargs=dict( - key_derivation=cookie_interface.key_derivation, digest_method=cookie_interface.digest_method + key_derivation=cookie_interface.key_derivation, + digest_method=cookie_interface.digest_method, ), ) diff --git a/website/achievements.py b/website/achievements.py index 78e6c56f9ab..6a979abe4a9 100644 --- a/website/achievements.py +++ b/website/achievements.py @@ -35,7 +35,9 @@ def get_all_commands(self): def get_global_statistics(self): all_achievements = self.db.get_all_achievements() statistics = {} - for achievement in self.translations.get_translations("en").get("achievements").keys(): + for achievement in ( + self.translations.get_translations("en").get("achievements").keys() + ): statistics[achievement] = 0 self.total_users = len(all_achievements) @@ -61,7 +63,9 @@ def initialize_user_data_if_necessary(self): if "commands" in achievements_data: # We convert the list to a set perform intersection with all commands and convert to set again # This to prevent "faulty" commands being kept from relic code (such as "multiplication") - session["commands"] = list(set(achievements_data["commands"]).intersection(self.all_commands)) + session["commands"] = list( + set(achievements_data["commands"]).intersection(self.all_commands) + ) else: session["commands"] = [] if "run_programs" in achievements_data: @@ -89,9 +93,11 @@ def increase_count(self, category): def add_single_achievement(self, username, achievement): """Record an achievement for this user, return the list of achievements earned so far.""" self.initialize_user_data_if_necessary() - if achievement not in session["achieved"] and achievement in self.translations.get_translations( - session["lang"] - ).get("achievements"): + if achievement not in session[ + "achieved" + ] and achievement in self.translations.get_translations(session["lang"]).get( + "achievements" + ): return self.verify_pushed_achievement(username, achievement) else: return None @@ -160,50 +166,99 @@ def verify_pushed_achievement(self, username, achievement): def get_earned_achievements(self): self.initialize_user_data_if_necessary() - translations = self.translations.get_translations(session["lang"]).get("achievements") + translations = self.translations.get_translations(session["lang"]).get( + "achievements" + ) translated_achievements = [] for achievement in session["new_achieved"]: - percentage = round(((self.statistics[achievement] / self.total_users) * 100), 2) + percentage = round( + ((self.statistics[achievement] / self.total_users) * 100), 2 + ) stats = safe_format(gettext("percentage_achieved"), percentage=percentage) translated_achievements.append( - [translations[achievement]["title"], translations[achievement]["text"], stats] + [ + translations[achievement]["title"], + translations[achievement]["text"], + stats, + ] ) - session["new_achieved"] = [] # Once we get earned achievements -> empty the array with "waiting" ones + session[ + "new_achieved" + ] = [] # Once we get earned achievements -> empty the array with "waiting" ones return translated_achievements def check_programs_run(self): self.initialize_user_data_if_necessary() - if "getting_started_I" not in session["achieved"] and session["run_programs"] >= 5: + if ( + "getting_started_I" not in session["achieved"] + and session["run_programs"] >= 5 + ): session["new_achieved"].append("getting_started_I") - if "getting_started_II" not in session["achieved"] and session["run_programs"] >= 10: + if ( + "getting_started_II" not in session["achieved"] + and session["run_programs"] >= 10 + ): session["new_achieved"].append("getting_started_II") - if "getting_started_III" not in session["achieved"] and session["run_programs"] >= 50: + if ( + "getting_started_III" not in session["achieved"] + and session["run_programs"] >= 50 + ): session["new_achieved"].append("getting_started_III") - if "getting_started_IV" not in session["achieved"] and session["run_programs"] >= 200: + if ( + "getting_started_IV" not in session["achieved"] + and session["run_programs"] >= 200 + ): session["new_achieved"].append("getting_started_IV") - if "getting_started_V" not in session["achieved"] and session["run_programs"] >= 500: + if ( + "getting_started_V" not in session["achieved"] + and session["run_programs"] >= 500 + ): session["new_achieved"].append("getting_started_V") def check_programs_saved(self): self.initialize_user_data_if_necessary() - if "one_to_remember_I" not in session["achieved"] and session["saved_programs"] >= 1: + if ( + "one_to_remember_I" not in session["achieved"] + and session["saved_programs"] >= 1 + ): session["new_achieved"].append("one_to_remember_I") - if "one_to_remember_II" not in session["achieved"] and session["saved_programs"] >= 5: + if ( + "one_to_remember_II" not in session["achieved"] + and session["saved_programs"] >= 5 + ): session["new_achieved"].append("one_to_remember_II") - if "one_to_remember_III" not in session["achieved"] and session["saved_programs"] >= 10: + if ( + "one_to_remember_III" not in session["achieved"] + and session["saved_programs"] >= 10 + ): session["new_achieved"].append("one_to_remember_III") - if "one_to_remember_IV" not in session["achieved"] and session["saved_programs"] >= 25: + if ( + "one_to_remember_IV" not in session["achieved"] + and session["saved_programs"] >= 25 + ): session["new_achieved"].append("one_to_remember_IV") - if "one_to_remember_V" not in session["achieved"] and session["saved_programs"] >= 50: + if ( + "one_to_remember_V" not in session["achieved"] + and session["saved_programs"] >= 50 + ): session["new_achieved"].append("one_to_remember_V") def check_programs_submitted(self): self.initialize_user_data_if_necessary() - if "deadline_daredevil_I" not in session["achieved"] and session["submitted_programs"] >= 1: + if ( + "deadline_daredevil_I" not in session["achieved"] + and session["submitted_programs"] >= 1 + ): session["new_achieved"].append("deadline_daredevil_I") - if "deadline_daredevil_II" not in session["achieved"] and session["submitted_programs"] >= 3: + if ( + "deadline_daredevil_II" not in session["achieved"] + and session["submitted_programs"] >= 3 + ): session["new_achieved"].append("deadline_daredevil_II") - if "deadline_daredevil_III" not in session["achieved"] and session["submitted_programs"] >= 10: + if ( + "deadline_daredevil_III" not in session["achieved"] + and session["submitted_programs"] >= 10 + ): session["new_achieved"].append("deadline_daredevil_III") def check_code_achievements(self, code, level): @@ -213,9 +268,14 @@ def check_code_achievements(self, code, level): for command in list(set(commands_in_code)): # To remove duplicates if command not in session["commands"] and command in self.all_commands: session["new_commands"].append(command) - if set(session["commands"]).union(set(session["new_commands"])) == self.all_commands: + if ( + set(session["commands"]).union(set(session["new_commands"])) + == self.all_commands + ): session["new_achieved"].append("trying_is_key") - if "did_you_say_please" not in session["achieved"] and "ask" in hedy.all_commands(code, level, session["lang"]): + if "did_you_say_please" not in session[ + "achieved" + ] and "ask" in hedy.all_commands(code, level, session["lang"]): session["new_achieved"].append("did_you_say_please") if ( "talk-talk-talk" not in session["achieved"] @@ -233,15 +293,25 @@ def check_code_achievements(self, code, level): def check_response_achievements(self, code, response): self.initialize_user_data_if_necessary() - if "ninja_turtle" not in session["achieved"] and "has_turtle" in response and response["has_turtle"]: + if ( + "ninja_turtle" not in session["achieved"] + and "has_turtle" in response + and response["has_turtle"] + ): session["new_achieved"].append("ninja_turtle") - if "watch_out" not in session["achieved"] and "Warning" in response and response["Warning"]: + if ( + "watch_out" not in session["achieved"] + and "Warning" in response + and response["Warning"] + ): session["new_achieved"].append("watch_out") if "Error" in response and response["Error"]: session["consecutive_errors"] += 1 if session["previous_code"] == code: if session["identical_consecutive_errors"] == 0: - session["identical_consecutive_errors"] += 2 # We have to count the first one too! + session[ + "identical_consecutive_errors" + ] += 2 # We have to count the first one too! else: session["identical_consecutive_errors"] += 1 if session["identical_consecutive_errors"] >= 3: @@ -249,7 +319,10 @@ def check_response_achievements(self, code, response): session["new_achieved"].append("programming_panic") session["previous_code"] = code else: - if "programming_protagonist" not in session["achieved"] and session["consecutive_errors"] >= 1: + if ( + "programming_protagonist" not in session["achieved"] + and session["consecutive_errors"] >= 1 + ): session["new_achieved"].append("programming_protagonist") session["consecutive_errors"] = 0 session["identical_consecutive_errors"] = 0 @@ -268,7 +341,9 @@ def push_new_achievement(self, user): self.achievements.initialize_user_data_if_necessary() if body["achievement"] not in session["achieved"] and body[ "achievement" - ] in self.achievements.translations.get_translations(session["lang"]).get("achievements"): + ] in self.achievements.translations.get_translations(session["lang"]).get( + "achievements" + ): return jsonify( { "achievements": self.achievements.verify_pushed_achievement( diff --git a/website/admin.py b/website/admin.py index cddbffc0347..120ecbfdc6e 100644 --- a/website/admin.py +++ b/website/admin.py @@ -32,7 +32,9 @@ def get_admin_page(self): # Todo TB: Why do we check for the testing_request here? (09-22) if not utils.is_testing_request(request) and not is_admin(current_user()): return utils.error_page(error=403, ui_message=gettext("unauthorized")) - return render_template("admin/admin.html", page_title=gettext("title_admin"), current_page="admin") + return render_template( + "admin/admin.html", page_title=gettext("title_admin"), current_page="admin" + ) @route("/users", methods=["GET"]) @requires_admin @@ -83,7 +85,11 @@ def get_admin_users_page(self, user): data["teacher_request"] = True if data["teacher_request"] else None data["third_party"] = True if data["third_party"] else None data["created"] = utils.timestamp_to_date(data["created"]) - data["last_login"] = utils.timestamp_to_date(data["last_login"]) if data.get("last_login") else None + data["last_login"] = ( + utils.timestamp_to_date(data["last_login"]) + if data.get("last_login") + else None + ) if category == "language": if language != data["language"]: continue @@ -94,19 +100,30 @@ def get_admin_users_page(self, user): if substring and substring not in data.get("username"): continue if category == "email": - if not data.get("email") or (substring and substring not in data.get("email")): + if not data.get("email") or ( + substring and substring not in data.get("email") + ): continue if category == "created": - if start_date and utils.string_date_to_date(start_date) > data["created"]: + if ( + start_date + and utils.string_date_to_date(start_date) > data["created"] + ): continue if end_date and utils.string_date_to_date(end_date) < data["created"]: continue if category == "last_login": if not data.get("last_login"): continue - if start_date and utils.string_date_to_date(start_date) > data["last_login"]: + if ( + start_date + and utils.string_date_to_date(start_date) > data["last_login"] + ): continue - if end_date and utils.string_date_to_date(end_date) < data["last_login"]: + if ( + end_date + and utils.string_date_to_date(end_date) < data["last_login"] + ): continue userdata.append(data) @@ -122,7 +139,7 @@ def get_admin_users_page(self, user): keyword_language_filter=keyword_language, next_page_token=users.next_page_token, current_page="admin", - javascript_page_options=dict(page='admin-users'), + javascript_page_options=dict(page="admin-users"), ) @route("/classes", methods=["GET"]) @@ -138,7 +155,9 @@ def get_admin_classes_page(self, user): "students": len(Class.get("students")) if "students" in Class else 0, "stats": statistics.get_general_class_stats(Class.get("students", [])), "id": Class.get("id"), - "weekly_runs": statistics.get_general_class_stats(Class.get("students", []))["week"]["runs"] + "weekly_runs": statistics.get_general_class_stats( + Class.get("students", []) + )["week"]["runs"], } for Class in self.db.all_classes() ] @@ -146,15 +165,19 @@ def get_admin_classes_page(self, user): active_classes = [x for x in classes if x["weekly_runs"] > 0] # classes = sorted(classes, key=lambda d: d["weekly_runs"], reverse=True) - return render_template("admin/admin-classes.html", - active_classes=active_classes, - classes=classes, - page_title=gettext("title_admin")) + return render_template( + "admin/admin-classes.html", + active_classes=active_classes, + classes=classes, + page_title=gettext("title_admin"), + ) @route("/adventures", methods=["GET"]) @requires_admin def get_admin_adventures_page(self, user): - all_adventures = sorted(self.db.all_adventures(), key=lambda d: d.get("date", 0), reverse=True) + all_adventures = sorted( + self.db.all_adventures(), key=lambda d: d.get("date", 0), reverse=True + ) adventures = [ { "id": adventure.get("id"), @@ -177,27 +200,37 @@ def get_admin_adventures_page(self, user): @route("/stats", methods=["GET"]) @requires_admin def get_admin_stats_page(self, user): - return render_template("admin/admin-stats.html", - page_title=gettext("title_admin"), - current_page="admin", - javascript_page_options=dict( - page='admin-stats', - )) + return render_template( + "admin/admin-stats.html", + page_title=gettext("title_admin"), + current_page="admin", + javascript_page_options=dict( + page="admin-stats", + ), + ) @route("/logs", methods=["GET"]) @requires_admin def get_admin_logs_page(self, user): - return render_template("admin/admin-logs.html", page_title=gettext("title_admin"), current_page="admin") + return render_template( + "admin/admin-logs.html", + page_title=gettext("title_admin"), + current_page="admin", + ) @route("/achievements", methods=["GET"]) @requires_admin def get_admin_achievements_page(self, user): stats = {} - achievements = hedyweb.AchievementTranslations().get_translations("en").get("achievements") + achievements = ( + hedyweb.AchievementTranslations().get_translations("en").get("achievements") + ) for achievement in achievements.keys(): stats[achievement] = {} stats[achievement]["name"] = achievements.get(achievement).get("title") - stats[achievement]["description"] = achievements.get(achievement).get("text") + stats[achievement]["description"] = achievements.get(achievement).get( + "text" + ) stats[achievement]["count"] = 0 user_achievements = self.db.get_all_achievements() @@ -251,7 +284,9 @@ def change_user_email(self, user): return gettext("ajax_error"), 400 if not isinstance(body.get("username"), str): return gettext("username_invalid"), 400 - if not isinstance(body.get("email"), str) or not utils.valid_email(body["email"]): + if not isinstance(body.get("email"), str) or not utils.valid_email( + body["email"] + ): return gettext("email_invalid"), 400 user = self.db.user_by_username(body["username"].strip().lower()) @@ -264,7 +299,10 @@ def change_user_email(self, user): # We assume that this email is not in use by any other users. # In other words, we trust the admin to enter a valid, not yet used email address. - self.db.update_user(user["username"], {"email": body["email"], "verification_pending": hashed_token}) + self.db.update_user( + user["username"], + {"email": body["email"], "verification_pending": hashed_token}, + ) # If this is an e2e test, we return the email verification token directly instead of emailing it. if utils.is_testing_request(request): @@ -324,13 +362,24 @@ def update_is_teacher(db: Database, user, is_teacher_value=1): user_is_teacher = is_teacher(user) user_becomes_teacher = is_teacher_value and not user_is_teacher - db.update_user(user["username"], {"is_teacher": is_teacher_value, "teacher_request": None}) + db.update_user( + user["username"], {"is_teacher": is_teacher_value, "teacher_request": None} + ) # Some (student users) may not have emails, and this code would explode otherwise - if user_becomes_teacher and not utils.is_testing_request(request) and user.get('email'): + if ( + user_becomes_teacher + and not utils.is_testing_request(request) + and user.get("email") + ): try: send_localized_email_template( - locale=user["language"], template="welcome_teacher", email=user["email"], username=user["username"] + locale=user["language"], + template="welcome_teacher", + email=user["email"], + username=user["username"], ) except Exception: - print(f"An error occurred when sending a welcome teacher mail to {user['email']}, changes still processed") + print( + f"An error occurred when sending a welcome teacher mail to {user['email']}, changes still processed" + ) diff --git a/website/auth.py b/website/auth.py index c37973c7ffd..0f734ebe7e5 100644 --- a/website/auth.py +++ b/website/auth.py @@ -48,8 +48,16 @@ def mailchimp_subscribe_user(email, country): # Request is always for teachers as only they can subscribe to newsletters - request_body = {"email_address": email, "status": "subscribed", "tags": [country, "teacher"]} - r = requests.post(MAILCHIMP_API_URL + "/members", headers=MAILCHIMP_API_HEADERS, data=json.dumps(request_body)) + request_body = { + "email_address": email, + "status": "subscribed", + "tags": [country, "teacher"], + } + r = requests.post( + MAILCHIMP_API_URL + "/members", + headers=MAILCHIMP_API_HEADERS, + data=json.dumps(request_body), + ) subscription_error = None if r.status_code != 200 and r.status_code != 400: @@ -63,7 +71,13 @@ def mailchimp_subscribe_user(email, country): config["email"]["sender"], "ERROR - Subscription to Hedy newsletter on signup", email, - "

" + email + "

Status:" + str(r.status_code) + "    Body:" + r.text + "
", + "

" + + email + + "

Status:"
+            + str(r.status_code)
+            + "    Body:"
+            + r.text
+            + "
", ) @@ -104,7 +118,9 @@ def remember_current_user(db_user): def pick(d, *requested_keys): - return {key: force_json_serializable_type(d.get(key, None)) for key in requested_keys} + return { + key: force_json_serializable_type(d.get(key, None)) for key in requested_keys + } def force_json_serializable_type(x): @@ -214,7 +230,7 @@ def requires_login_redirect(f): @wraps(f) def inner(*args, **kws): if not is_user_logged_in(): - return redirect('/') + return redirect("/") # The reason we pass by keyword argument is to make this # work logically both for free-floating functions as well # as [unbound] class methods. @@ -293,7 +309,9 @@ def validate_signup_data(account): return gettext("username_special") if len(account.get("username").strip()) < 3: return gettext("username_three") - if not isinstance(account.get("email"), str) or not utils.valid_email(account.get("email")): + if not isinstance(account.get("email"), str) or not utils.valid_email( + account.get("email") + ): return gettext("email_invalid") if not isinstance(account.get("password"), str): return gettext("password_invalid") @@ -387,7 +405,9 @@ def send_email_template(template, email, link=None, username=None): body_html = body_html.format(content=body) body_plain = body if link: - body_plain = safe_format(body_plain, link=gettext("copy_mail_link") + " " + link) + body_plain = safe_format( + body_plain, link=gettext("copy_mail_link") + " " + link + ) body_html = safe_format(body_html, link='{link}') body_html = safe_format(body_html, link=gettext("link")) @@ -406,7 +426,9 @@ def send_localized_email_template(locale, template, email, link=None, username=N def store_new_student_account(db, account, teacher_username): - username, hashed, hashed_token = prepare_user_db(account["username"], account["password"]) + username, hashed, hashed_token = prepare_user_db( + account["username"], account["password"] + ) user = { "username": username, "password": hashed, @@ -433,7 +455,9 @@ def prepare_user_db(username, password): def create_verify_link(username, token): email = email_base_url() + "/auth/verify?username=" - email += urllib.parse.quote_plus(username) + "&token=" + urllib.parse.quote_plus(token) + email += ( + urllib.parse.quote_plus(username) + "&token=" + urllib.parse.quote_plus(token) + ) return email @@ -455,5 +479,7 @@ def email_base_url(): def create_recover_link(username, token): email = email_base_url() + "/reset?username=" - email += urllib.parse.quote_plus(username) + "&token=" + urllib.parse.quote_plus(token) + email += ( + urllib.parse.quote_plus(username) + "&token=" + urllib.parse.quote_plus(token) + ) return email diff --git a/website/auth_pages.py b/website/auth_pages.py index 665798f19d5..7b81cc95d7d 100644 --- a/website/auth_pages.py +++ b/website/auth_pages.py @@ -57,7 +57,10 @@ def login(self): user = self.db.user_by_username(body["username"]) if not user or not check_password(body["password"], user["password"]): - return gettext("invalid_username_password") + " " + gettext("no_account"), 403 + return ( + gettext("invalid_username_password") + " " + gettext("no_account"), + 403, + ) # If the number of bcrypt rounds has changed, create a new hash. new_hash = None @@ -65,7 +68,13 @@ def login(self): new_hash = password_hash(body["password"], make_salt()) cookie = make_salt() - self.db.store_token({"id": cookie, "username": user["username"], "ttl": times() + SESSION_LENGTH}) + self.db.store_token( + { + "id": cookie, + "username": user["username"], + "ttl": times() + SESSION_LENGTH, + } + ) if new_hash: self.db.record_login(user["username"], new_hash) else: @@ -121,13 +130,21 @@ def signup(self): return validation, 400 # Validate fields only relevant when creating a single user account - if not isinstance(body.get("password_repeat"), str) or body["password"] != body["password_repeat"]: + if ( + not isinstance(body.get("password_repeat"), str) + or body["password"] != body["password_repeat"] + ): return gettext("repeat_match_password"), 400 - if not isinstance(body.get("language"), str) or body.get("language") not in ALL_LANGUAGES.keys(): + if ( + not isinstance(body.get("language"), str) + or body.get("language") not in ALL_LANGUAGES.keys() + ): return gettext("language_invalid"), 400 if not isinstance(body.get("agree_terms"), str) or not body.get("agree_terms"): return gettext("agree_invalid"), 400 - if not isinstance(body.get("keyword_language"), str) or body.get("keyword_language") not in [ + if not isinstance(body.get("keyword_language"), str) or body.get( + "keyword_language" + ) not in [ "en", body.get("language"), ]: @@ -140,10 +157,18 @@ def signup(self): body["birth_year"] = int(body.get("birth_year")) except ValueError: return safe_format(gettext("year_invalid"), current_year=str(year)), 400 - if not isinstance(body.get("birth_year"), int) or body["birth_year"] <= 1900 or body["birth_year"] > year: + if ( + not isinstance(body.get("birth_year"), int) + or body["birth_year"] <= 1900 + or body["birth_year"] > year + ): return safe_format(gettext("year_invalid"), current_year=str(year)), 400 if "gender" in body: - if body["gender"] != "m" and body["gender"] != "f" and body["gender"] != "o": + if ( + body["gender"] != "m" + and body["gender"] != "f" + and body["gender"] != "o" + ): return gettext("gender_invalid"), 400 if "country" in body: if not body["country"] in COUNTRIES: @@ -154,8 +179,13 @@ def signup(self): if not isinstance(body["heard_about"], list): return gettext("heard_about_invalid"), 400 for option in body["heard_about"]: - if option not in ["from_another_teacher", "social_media", "from_video", "from_magazine_website", - "other_source"]: + if option not in [ + "from_another_teacher", + "social_media", + "from_video", + "from_magazine_website", + "other_source", + ]: return gettext("heard_about_invalid"), 400 if "prog_experience" in body and body["prog_experience"] not in ["yes", "no"]: return gettext("experience_invalid"), 400 @@ -191,7 +221,13 @@ def signup(self): # We automatically login the user cookie = make_salt() - self.db.store_token({"id": cookie, "username": user["username"], "ttl": times() + SESSION_LENGTH}) + self.db.store_token( + { + "id": cookie, + "username": user["username"], + "ttl": times() + SESSION_LENGTH, + } + ) # We set the cookie to expire in a year, # just so that the browser won't invalidate it if the same cookie gets renewed by constant use. # The server will decide whether the cookie expires. @@ -208,7 +244,7 @@ def signup(self): remember_current_user(user) return resp - @ route("/verify", methods=["GET"]) + @route("/verify", methods=["GET"]) def verify_email(self): username = request.args.get("username", None) token = request.args.get("token", None) @@ -235,35 +271,41 @@ def verify_email(self): # We automatically login the user cookie = make_salt() - self.db.store_token({"id": cookie, "username": user["username"], "ttl": times() + SESSION_LENGTH}) + self.db.store_token( + { + "id": cookie, + "username": user["username"], + "ttl": times() + SESSION_LENGTH, + } + ) remember_current_user(user) return redirect("/landing-page") - @ route("/logout", methods=["POST"]) + @route("/logout", methods=["POST"]) def logout(self): forget_current_user() if request.cookies.get(TOKEN_COOKIE_NAME): self.db.forget_token(request.cookies.get(TOKEN_COOKIE_NAME)) return "", 200 - @ route("/destroy", methods=["POST"]) - @ requires_login + @route("/destroy", methods=["POST"]) + @requires_login def destroy(self, user): forget_current_user() self.db.forget_token(request.cookies.get(TOKEN_COOKIE_NAME)) self.db.forget_user(user["username"]) return "", 200 - @ route("/destroy_public", methods=["POST"]) - @ requires_login + @route("/destroy_public", methods=["POST"]) + @requires_login def destroy_public(self, user): self.db.forget_public_profile(user["username"]) session.pop("profile_image", None) # Delete profile image id if existing return "", 200 - @ route("/change_student_password", methods=["POST"]) - @ requires_login + @route("/change_student_password", methods=["POST"]) + @requires_login def change_student_password(self, user): body = request.json if not isinstance(body, dict): @@ -286,14 +328,16 @@ def change_student_password(self, user): return {"success": gettext("password_change_success")}, 200 - @ route("/change_password", methods=["POST"]) - @ requires_login + @route("/change_password", methods=["POST"]) + @requires_login def change_password(self, user): body = request.json if not isinstance(body, dict): return gettext("ajax_error"), 400 - if not isinstance(body.get("old_password"), str) or not isinstance(body.get("new-password"), str): + if not isinstance(body.get("old_password"), str) or not isinstance( + body.get("new-password"), str + ): return gettext("password_invalid"), 400 if not isinstance(body.get("password_repeat"), str): return gettext("repeat_match_password"), 400 @@ -314,13 +358,17 @@ def change_password(self, user): # We are not updating the user in the Flask session, because we should not rely on the password in anyway. if not is_testing_request(request): try: - send_email_template(template="change_password", email=user["email"], username=user["username"]) + send_email_template( + template="change_password", + email=user["email"], + username=user["username"], + ) except BaseException: return gettext("mail_error_change_processed"), 400 return jsonify({"message": gettext("password_updated")}), 200 - @ route("/recover", methods=["POST"]) + @route("/recover", methods=["POST"]) def recover(self): body = request.json # Validations @@ -348,7 +396,9 @@ def recover(self): # Create a token -> use the reset_length value as we don't want the token to live as long as a login one token = make_salt() # Todo TB -> Don't we want to use a hashed token here as well? - self.db.store_token({"id": token, "username": user["username"], "ttl": times() + RESET_LENGTH}) + self.db.store_token( + {"id": token, "username": user["username"], "ttl": times() + RESET_LENGTH} + ) if is_testing_request(request): # If this is an e2e test, we return the email verification token directly instead of emailing it. @@ -366,7 +416,7 @@ def recover(self): return jsonify({"message": gettext("sent_password_recovery")}), 200 - @ route("/reset", methods=["POST"]) + @route("/reset", methods=["POST"]) def reset(self): body = request.json # Validations @@ -380,11 +430,18 @@ def reset(self): return gettext("password_invalid"), 400 if len(body["password"]) < 6: return gettext("password_six"), 400 - if not isinstance(body.get("password_repeat"), str) or body["password"] != body["password_repeat"]: + if ( + not isinstance(body.get("password_repeat"), str) + or body["password"] != body["password_repeat"] + ): return gettext("repeat_match_password"), 400 token = self.db.get_token(body["token"]) - if not token or body["token"] != token.get("id") or body["username"] != token.get("username"): + if ( + not token + or body["token"] != token.get("id") + or body["username"] != token.get("username") + ): return gettext("token_invalid"), 403 hashed = password_hash(body["password"], make_salt()) @@ -403,7 +460,9 @@ def reset(self): if not is_testing_request(request): try: - send_email_template(template="reset_password", email=email, username=user["username"]) + send_email_template( + template="reset_password", email=email, username=user["username"] + ) except BaseException: return gettext("mail_error_change_processed"), 400 @@ -422,7 +481,9 @@ def request_teacher_account(self, user): return jsonify({"message": gettext("teacher_account_success")}), 200 def store_new_account(self, account, email): - username, hashed, hashed_token = prepare_user_db(account["username"], account["password"]) + username, hashed, hashed_token = prepare_user_db( + account["username"], account["password"] + ) user = { "username": username, "password": hashed, @@ -436,8 +497,15 @@ def store_new_account(self, account, email): "last_login": timems(), } - for field in ["country", "birth_year", "gender", "language", "heard_about", "prog_experience", - "experience_languages"]: + for field in [ + "country", + "birth_year", + "gender", + "language", + "heard_about", + "prog_experience", + "experience_languages", + ]: if field in account: if field == "heard_about" and len(account[field]) == 0: continue @@ -460,6 +528,8 @@ def store_new_account(self, account, email): username=user["username"], ) except BaseException: - return user, make_response({gettext("mail_error_change_processed")}, 400) + return user, make_response( + {gettext("mail_error_change_processed")}, 400 + ) resp = make_response({}) return user, resp diff --git a/website/aws_helpers.py b/website/aws_helpers.py index 1322161d50c..ee3ce2c7b7d 100644 --- a/website/aws_helpers.py +++ b/website/aws_helpers.py @@ -14,10 +14,14 @@ def s3_querylog_transmitter_from_env(): """Return an S3 transmitter, or return None.""" - have_aws_creds = os.getenv("AWS_ACCESS_KEY_ID") and os.getenv("AWS_SECRET_ACCESS_KEY") + have_aws_creds = os.getenv("AWS_ACCESS_KEY_ID") and os.getenv( + "AWS_SECRET_ACCESS_KEY" + ) if not have_aws_creds: - logger.warning("Unable to initialize S3 querylogger (missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY)") + logger.warning( + "Unable to initialize S3 querylogger (missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY)" + ) return None return make_s3_transmitter(config.config["s3-query-logs"]) @@ -25,10 +29,14 @@ def s3_querylog_transmitter_from_env(): def s3_parselog_transmitter_from_env(): """Return an S3 transmitter, or return None.""" - have_aws_creds = os.getenv("AWS_ACCESS_KEY_ID") and os.getenv("AWS_SECRET_ACCESS_KEY") + have_aws_creds = os.getenv("AWS_ACCESS_KEY_ID") and os.getenv( + "AWS_SECRET_ACCESS_KEY" + ) if not have_aws_creds: - logger.warning("Unable to initialize S3 parse logger (missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY)") + logger.warning( + "Unable to initialize S3 parse logger (missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY)" + ) return None return make_s3_transmitter(config.config["s3-parse-logs"]) @@ -64,8 +72,13 @@ def transmit_to_s3(timestamp, records): body = "\n".join(json.dumps(r) for r in records) s3.put_object( - Bucket=s3config["bucket"], Key=key, StorageClass="STANDARD_IA", Body=body # Cheaper, applicable for logs + Bucket=s3config["bucket"], + Key=key, + StorageClass="STANDARD_IA", + Body=body, # Cheaper, applicable for logs + ) + logger.debug( + f'Wrote {len(records)} query logs to s3://{s3config["bucket"]}/{key}' ) - logger.debug(f'Wrote {len(records)} query logs to s3://{s3config["bucket"]}/{key}') return transmit_to_s3 diff --git a/website/cdn.py b/website/cdn.py index 09b13c7edcd..bd9e7ab9f94 100644 --- a/website/cdn.py +++ b/website/cdn.py @@ -43,7 +43,9 @@ def __init__(self, app, cdn_prefix, commit): # emails and content we forgot to replace or are unable to replace (like in Markdowns). self.static_prefix = "/static-" + commit app.add_url_rule( - self.static_prefix + "/", endpoint="cdn_static", view_func=self._send_static_file + self.static_prefix + "/", + endpoint="cdn_static", + view_func=self._send_static_file, ) app.add_template_global(self.static, name="static") diff --git a/website/classes.py b/website/classes.py index 0b1524b5d86..c76af93ed60 100644 --- a/website/classes.py +++ b/website/classes.py @@ -6,7 +6,13 @@ import utils from config import config from website.flask_helpers import render_template -from website.auth import current_user, is_teacher, requires_login, requires_teacher, refresh_current_user_from_db +from website.auth import ( + current_user, + is_teacher, + requires_login, + requires_teacher, + refresh_current_user_from_db, +) from .achievements import Achievements from .database import Database @@ -52,7 +58,9 @@ def create_class(self, user): } self.db.store_class(Class) - achievement = self.achievements.add_single_achievement(user["username"], "ready_set_education") + achievement = self.achievements.add_single_achievement( + user["username"], "ready_set_education" + ) if achievement: return {"id": Class["id"], "achievement": achievement}, 200 return {"id": Class["id"]}, 200 @@ -77,10 +85,15 @@ def update_class(self, user, class_id): Classes = self.db.get_teacher_classes(user["username"], True) for Class in Classes: if Class["name"] == body["name"]: - return "duplicate", 200 # Todo TB: Will have to look into this, but not sure why we return a 200? + return ( + "duplicate", + 200, + ) # Todo TB: Will have to look into this, but not sure why we return a 200? self.db.update_class(class_id, body["name"]) - achievement = self.achievements.add_single_achievement(user["username"], "on_second_thoughts") + achievement = self.achievements.add_single_achievement( + user["username"], "on_second_thoughts" + ) if achievement: return {"achievement": achievement}, 200 return {}, 200 @@ -93,7 +106,9 @@ def delete_class(self, user, class_id): return gettext("no_such_class"), 404 self.db.delete_class(Class) - achievement = self.achievements.add_single_achievement(user["username"], "end_of_semester") + achievement = self.achievements.add_single_achievement( + user["username"], "end_of_semester" + ) if achievement: return {"achievement": achievement}, 200 return {}, 200 @@ -142,7 +157,9 @@ def join_class(self): # Also remove the pending message in this case session["messages"] = 0 - achievement = self.achievements.add_single_achievement(current_user()["username"], "epic_education") + achievement = self.achievements.add_single_achievement( + current_user()["username"], "epic_education" + ) if achievement: return {"achievement": achievement}, 200 return {}, 200 @@ -151,14 +168,18 @@ def join_class(self): @requires_login def leave_class(self, user, class_id, student_id): Class = self.db.get_class(class_id) - if not Class or (Class["teacher"] != user["username"] and student_id != user["username"]): + if not Class or ( + Class["teacher"] != user["username"] and student_id != user["username"] + ): return gettext("ajax_error"), 400 self.db.remove_student_from_class(Class["id"], student_id) refresh_current_user_from_db() achievement = None if Class["teacher"] == user["username"]: - achievement = self.achievements.add_single_achievement(user["username"], "detention") + achievement = self.achievements.add_single_achievement( + user["username"], "detention" + ) if achievement: return {"achievement": achievement}, 200 return {}, 200 @@ -223,7 +244,9 @@ def duplicate_class(self, user): customizations["id"] = class_id self.db.update_class_customizations(customizations) - achievement = self.achievements.add_single_achievement(current_user()["username"], "one_for_money") + achievement = self.achievements.add_single_achievement( + current_user()["username"], "one_for_money" + ) if achievement: return {"achievement": achievement}, 200 @@ -283,9 +306,13 @@ def remove_invite(self, user): # Fixme TB -> Sure the user is also allowed to remove their invite, but why the 'retrieve_class_error'? if not is_teacher(user) and username != user.get("username"): - return utils.error_page(error=403, ui_message=gettext("retrieve_class_error")) + return utils.error_page( + error=403, ui_message=gettext("retrieve_class_error") + ) Class = self.db.get_class(class_id) - if not Class or (Class["teacher"] != user["username"] and username != user.get("username")): + if not Class or ( + Class["teacher"] != user["username"] and username != user.get("username") + ): return utils.error_page(error=404, ui_message=gettext("no_such_class")) self.db.remove_class_invite(username) @@ -297,5 +324,8 @@ def resolve_class_link(self, link_id): if not Class: return utils.error_page(error=404, ui_message=gettext("invalid_class_link")) return redirect( - request.url.replace("/hedy/l/" + link_id, "/class/" + Class["id"] + "/prejoin/" + link_id), code=302 + request.url.replace( + "/hedy/l/" + link_id, "/class/" + Class["id"] + "/prejoin/" + link_id + ), + code=302, ) diff --git a/website/database.py b/website/database.py index 74af3a77e63..7568e36a4d3 100644 --- a/website/database.py +++ b/website/database.py @@ -6,30 +6,48 @@ from . import dynamo -storage = dynamo.AwsDynamoStorage.from_env() or dynamo.MemoryStorage("dev_database.json") - -USERS = dynamo.Table(storage, "users", "username", indexes=[ - dynamo.Index("email"), - dynamo.Index("epoch", sort_key="created") -]) -TOKENS = dynamo.Table(storage, "tokens", "id", indexes=[ - dynamo.Index('id'), - dynamo.Index('username'), -]) -PROGRAMS = dynamo.Table(storage, "programs", "id", indexes=[ - dynamo.Index('username', sort_key='date', index_name='username-index'), - dynamo.Index('public', sort_key='date', index_name='public-index'), - dynamo.Index('hedy_choice', sort_key='date', index_name='hedy_choice-index'), - - # For the filtered view of the 'explore' page (keys_only so we don't duplicate other attributes unnecessarily) - dynamo.Index('lang', sort_key='date', keys_only=True), - dynamo.Index('level', sort_key='date', keys_only=True), - dynamo.Index('adventure_name', sort_key='date', keys_only=True), -]) -CLASSES = dynamo.Table(storage, "classes", "id", indexes=[ - dynamo.Index('teacher'), - dynamo.Index('link'), -]) +storage = dynamo.AwsDynamoStorage.from_env() or dynamo.MemoryStorage( + "dev_database.json" +) + +USERS = dynamo.Table( + storage, + "users", + "username", + indexes=[dynamo.Index("email"), dynamo.Index("epoch", sort_key="created")], +) +TOKENS = dynamo.Table( + storage, + "tokens", + "id", + indexes=[ + dynamo.Index("id"), + dynamo.Index("username"), + ], +) +PROGRAMS = dynamo.Table( + storage, + "programs", + "id", + indexes=[ + dynamo.Index("username", sort_key="date", index_name="username-index"), + dynamo.Index("public", sort_key="date", index_name="public-index"), + dynamo.Index("hedy_choice", sort_key="date", index_name="hedy_choice-index"), + # For the filtered view of the 'explore' page (keys_only so we don't duplicate other attributes unnecessarily) + dynamo.Index("lang", sort_key="date", keys_only=True), + dynamo.Index("level", sort_key="date", keys_only=True), + dynamo.Index("adventure_name", sort_key="date", keys_only=True), + ], +) +CLASSES = dynamo.Table( + storage, + "classes", + "id", + indexes=[ + dynamo.Index("teacher"), + dynamo.Index("link"), + ], +) # A custom teacher adventure # - id (str): id of the adventure @@ -39,9 +57,14 @@ # - level (int | str): level number, sometimes as an int, sometimes as a str # - name (str): adventure name # - public (bool): whether it can be shared -ADVENTURES = dynamo.Table(storage, "adventures", "id", indexes=[dynamo.Index("creator")]) +ADVENTURES = dynamo.Table( + storage, "adventures", "id", indexes=[dynamo.Index("creator")] +) INVITATIONS = dynamo.Table( - storage, "class_invitations", partition_key="username", indexes=[dynamo.Index("class_id")] + storage, + "class_invitations", + partition_key="username", + indexes=[dynamo.Index("class_id")], ) # Class customizations @@ -106,7 +129,9 @@ # 'levelAttempt' is a combination of level and attemptId, to distinguish attempts # by a user. 'level' is padded to 4 characters, then attemptId is added. # -QUIZ_ANSWERS = dynamo.Table(storage, "quizAnswers", partition_key="user", sort_key="levelAttempt") +QUIZ_ANSWERS = dynamo.Table( + storage, "quizAnswers", partition_key="user", sort_key="levelAttempt" +) # Holds information about program runs: success/failure and produced exceptions. Entries are created per user per level # per week and updated in place. Uses a composite partition key 'id#level' and 'week' as a sort key. Structure: @@ -119,16 +144,26 @@ # } # PROGRAM_STATS = dynamo.Table( - storage, "program-stats", partition_key="id#level", sort_key="week", indexes=[dynamo.Index("id", "week")] + storage, + "program-stats", + partition_key="id#level", + sort_key="week", + indexes=[dynamo.Index("id", "week")], ) QUIZ_STATS = dynamo.Table( - storage, "quiz-stats", partition_key="id#level", sort_key="week", indexes=[dynamo.Index("id", "week")] + storage, + "quiz-stats", + partition_key="id#level", + sort_key="week", + indexes=[dynamo.Index("id", "week")], ) class Database: - def record_quiz_answer(self, attempt_id, username, level, question_number, answer, is_correct): + def record_quiz_answer( + self, attempt_id, username, level, question_number, answer, is_correct + ): """Update the current quiz record with a new answer. Uses a DynamoDB update to add to the exising record. Expects answer to be A, B, C etc. @@ -153,7 +188,9 @@ def record_quiz_answer(self, attempt_id, username, level, question_number, answe def get_quiz_answer(self, username, level, attempt_id): """Load a quiz answer from the database.""" - quizAnswers = QUIZ_ANSWERS.get({"user": username, "levelAttempt": str(level).zfill(4) + "_" + attempt_id}) + quizAnswers = QUIZ_ANSWERS.get( + {"user": username, "levelAttempt": str(level).zfill(4) + "_" + attempt_id} + ) array_quiz_answers = [] for question_number in range(len(quizAnswers)): @@ -179,8 +216,8 @@ def last_level_programs_for_user(self, username, level): programs = self.level_programs_for_user(username, level) ret = {} for program in programs: - key = program.get('adventure_name', 'default') - if key not in ret or ret[key]['date'] < program['date']: + key = program.get("adventure_name", "default") + if key not in ret or ret[key]["date"] < program["date"]: ret[key] = program return ret @@ -191,24 +228,39 @@ def programs_for_user(self, username): """ return PROGRAMS.get_many({"username": username}, reverse=True) - def filtered_programs_for_user(self, username, level=None, adventure=None, submitted=None, - limit=None, pagination_token=None): + def filtered_programs_for_user( + self, + username, + level=None, + adventure=None, + submitted=None, + limit=None, + pagination_token=None, + ): ret = [] # FIXME: Query by index, the current behavior is slow for many programs # (See https://github.com/hedyorg/hedy/issues/4121) - programs = dynamo.GetManyIterator(PROGRAMS, {"username": username}, - reverse=True, limit=limit, pagination_token=pagination_token) + programs = dynamo.GetManyIterator( + PROGRAMS, + {"username": username}, + reverse=True, + limit=limit, + pagination_token=pagination_token, + ) for program in programs: - if level and program.get('level') != int(level): + if level and program.get("level") != int(level): continue if adventure: - if adventure == 'default' and program.get('adventure_name') != '': + if adventure == "default" and program.get("adventure_name") != "": continue - if adventure != 'default' and program.get('adventure_name') != adventure: + if ( + adventure != "default" + and program.get("adventure_name") != adventure + ): continue if submitted is not None: - if program.get('submitted') != submitted: + if program.get("submitted") != submitted: continue ret.append(program) @@ -220,8 +272,13 @@ def filtered_programs_for_user(self, username, level=None, adventure=None, submi def public_programs_for_user(self, username, limit=None, pagination_token=None): # Only return programs that are public but not submitted - programs = dynamo.GetManyIterator(PROGRAMS, {"username": username}, - reverse=True, limit=limit, pagination_token=pagination_token) + programs = dynamo.GetManyIterator( + PROGRAMS, + {"username": username}, + reverse=True, + limit=limit, + pagination_token=pagination_token, + ) ret = [] for program in programs: if program.get("public") != 1 or program.get("submitted", False): @@ -248,8 +305,11 @@ def store_program(self, program): Add an additional indexable field: 'username_level'. """ PROGRAMS.create( - dict(program, - username_level=f"{program.get('username')}-{program.get('level')}")) + dict( + program, + username_level=f"{program.get('username')}-{program.get('level')}", + ) + ) return program @@ -293,7 +353,9 @@ def store_student_adventure(self, student_adventure): def increase_user_program_count(self, username, delta=1): """Increase the program count of a user by the given delta.""" - return USERS.update({"username": username}, {"program_count": dynamo.DynamoIncrement(delta)}) + return USERS.update( + {"username": username}, {"program_count": dynamo.DynamoIncrement(delta)} + ) def user_by_username(self, username): """Return a user object from the username.""" @@ -330,7 +392,9 @@ def store_user(self, user): def record_login(self, username, new_password_hash=None): """Record the fact that the user logged in, potentially updating their password hash.""" if new_password_hash: - self.update_user(username, {"password": new_password_hash, "last_login": timems()}) + self.update_user( + username, {"password": new_password_hash, "last_login": timems()} + ) else: self.update_user(username, {"last_login": timems()}) @@ -371,20 +435,31 @@ def all_users(self, page_token=None): limit = 500 epoch, pagination_token = ( - page_token.split(":", maxsplit=1) if page_token is not None else (CURRENT_USER_EPOCH, None) + page_token.split(":", maxsplit=1) + if page_token is not None + else (CURRENT_USER_EPOCH, None) ) epoch = int(epoch) - page = USERS.get_many(dict(epoch=epoch), pagination_token=pagination_token, limit=limit, reverse=True) + page = USERS.get_many( + dict(epoch=epoch), + pagination_token=pagination_token, + limit=limit, + reverse=True, + ) # If we are not currently at epoch > 1 and there are no more records in the current # epoch, also include the first page of the next epoch. if not page.next_page_token and epoch > 1: epoch -= 1 - next_epoch_page = USERS.get_many(dict(epoch=epoch), reverse=True, limit=limit) + next_epoch_page = USERS.get_many( + dict(epoch=epoch), reverse=True, limit=limit + ) # Build a new result page with both sets of records, ending with the next "next page" token - page = dynamo.ResultPage(list(page) + list(next_epoch_page), next_epoch_page.next_page_token) + page = dynamo.ResultPage( + list(page) + list(next_epoch_page), next_epoch_page.next_page_token + ) # Prepend the epoch to the next pagination token if page.next_page_token: @@ -395,20 +470,24 @@ def get_all_public_programs(self): programs = PROGRAMS.get_many({"public": 1}, reverse=True) return [x for x in programs if not x.get("submitted", False)] - def get_public_programs(self, level_filter=None, language_filter=None, adventure_filter=None, limit=40): + def get_public_programs( + self, level_filter=None, language_filter=None, adventure_filter=None, limit=40 + ): """Return the most recent N public programs, optionally filtered by attributes. Walk down three key-only indexes at the same time until we have accumulated enough programs. """ filters = [] if level_filter: - filters.append(PROGRAMS.get_all({'level': int(level_filter)}, reverse=True)) + filters.append(PROGRAMS.get_all({"level": int(level_filter)}, reverse=True)) if language_filter: - filters.append(PROGRAMS.get_all({'lang': language_filter}, reverse=True)) + filters.append(PROGRAMS.get_all({"lang": language_filter}, reverse=True)) if adventure_filter: - filters.append(PROGRAMS.get_all({'adventure_name': adventure_filter}, reverse=True)) + filters.append( + PROGRAMS.get_all({"adventure_name": adventure_filter}, reverse=True) + ) - programs = dynamo.GetManyIterator(PROGRAMS, {'public': 1}, reverse=True) + programs = dynamo.GetManyIterator(PROGRAMS, {"public": 1}, reverse=True) # Iterate down programs, filtering down by the filters in 'filters' as we go to make sure # the programs match the filter. This works because they all have a 'matching' date field @@ -432,11 +511,11 @@ def get_public_programs(self, level_filter=None, language_filter=None, adventure # timestamp, but for the purposes of showing a sampling of public programs # I don't really care. for flt in filters: - while flt and flt.current['date'] > program['date']: + while flt and flt.current["date"] > program["date"]: flt.advance() # Include the current program in the result set if it is now the front item in each filter. - if all((flt and flt.current['id'] == program['id']) for flt in filters): + if all((flt and flt.current["id"] == program["id"]) for flt in filters): found_programs.append(program) return found_programs @@ -448,11 +527,13 @@ def add_public_profile_information(self, programs): Modifies the list in-place. """ - queries = {p['id']: {'username': p['username'].strip().lower()} for p in programs} + queries = { + p["id"]: {"username": p["username"].strip().lower()} for p in programs + } profiles = PUBLIC_PROFILES.batch_get(queries) for program in programs: - program['public_user'] = True if profiles[program['id']] else None + program["public_user"] = True if profiles[program["id"]] else None def get_highscores(self, username, filter, filter_value=None): profiles = [] @@ -471,15 +552,21 @@ def get_highscores(self, username, filter, filter_value=None): # If the user doesn't have a public profile the situation depends on the customizations # If the teacher has allowed the "all public" function -> add dummy profile to make all visible # Give the profile an extra attribute to clarify we don't update any non-existing public-profile - elif customizations and "all_highscores" in customizations.get("other_settings", []): + elif customizations and "all_highscores" in customizations.get( + "other_settings", [] + ): profiles.append({"username": student, "no_public_profile": True}) for profile in profiles: if not profile.get("country"): try: - country = self.user_by_username(profile.get("username")).get("country") + country = self.user_by_username(profile.get("username")).get( + "country" + ) if not profile.get("no_public_profile"): - self.update_country_public_profile(profile.get("username"), country) + self.update_country_public_profile( + profile.get("username"), country + ) except AttributeError: print("This profile username is invalid...") country = None @@ -487,7 +574,9 @@ def get_highscores(self, username, filter, filter_value=None): if not profile.get("achievements"): achievements = self.achievements_by_username(profile.get("username")) if not profile.get("no_public_profile"): - self.update_achievements_public_profile(profile.get("username"), len(achievements) or 0) + self.update_achievements_public_profile( + profile.get("username"), len(achievements) or 0 + ) else: # As the last achievement timestamp is stored on the public profile -> create an artificial one # We don't have a choice, otherwise the double sorting below will crash @@ -500,7 +589,11 @@ def get_highscores(self, username, filter, filter_value=None): profiles = [x for x in profiles if x.get("country") == filter_value] # Perform a double sorting: first by achievements (high-low), then by timestamp (low-high) - profiles = sorted(profiles, key=lambda k: (k.get("achievements"), -k.get("last_achievement")), reverse=True) + profiles = sorted( + profiles, + key=lambda k: (k.get("achievements"), -k.get("last_achievement")), + reverse=True, + ) # Add ranking for each profile ranking = 1 @@ -608,13 +701,22 @@ def update_class(self, id, name): def add_student_to_class(self, class_id, student_id): """Adds a student to a class.""" - CLASSES.update({"id": class_id}, {"students": dynamo.DynamoAddToStringSet(student_id)}) - USERS.update({"username": student_id}, {"classes": dynamo.DynamoAddToStringSet(class_id)}) + CLASSES.update( + {"id": class_id}, {"students": dynamo.DynamoAddToStringSet(student_id)} + ) + USERS.update( + {"username": student_id}, {"classes": dynamo.DynamoAddToStringSet(class_id)} + ) def remove_student_from_class(self, class_id, student_id): """Removes a student from a class.""" - CLASSES.update({"id": class_id}, {"students": dynamo.DynamoRemoveFromStringSet(student_id)}) - USERS.update({"username": student_id}, {"classes": dynamo.DynamoRemoveFromStringSet(class_id)}) + CLASSES.update( + {"id": class_id}, {"students": dynamo.DynamoRemoveFromStringSet(student_id)} + ) + USERS.update( + {"username": student_id}, + {"classes": dynamo.DynamoRemoveFromStringSet(class_id)}, + ) def delete_class(self, Class): for student_id in Class.get("students", []): @@ -661,7 +763,9 @@ def get_student_class_customizations(self, user): """ student_classes = self.get_student_classes(user) if student_classes: - class_customizations = self.get_class_customizations(student_classes[0]["id"]) + class_customizations = self.get_class_customizations( + student_classes[0]["id"] + ) return class_customizations or {} return {} @@ -690,7 +794,9 @@ def add_achievement_to_username(self, username, achievement): user_achievements["achieved"].append(achievement) ACHIEVEMENTS.put(user_achievements) # Update the amount of achievements on the public profile (if exists) - self.update_achievements_public_profile(username, len(user_achievements["achieved"])) + self.update_achievements_public_profile( + username, len(user_achievements["achieved"]) + ) if new_user: return True return False @@ -706,11 +812,15 @@ def add_achievements_to_username(self, username, achievements): for achievement in achievements: if achievement not in user_achievements["achieved"]: user_achievements["achieved"].append(achievement) - user_achievements["achieved"] = list(dict.fromkeys(user_achievements["achieved"])) + user_achievements["achieved"] = list( + dict.fromkeys(user_achievements["achieved"]) + ) ACHIEVEMENTS.put(user_achievements) # Update the amount of achievements on the public profile (if exists) - self.update_achievements_public_profile(username, len(user_achievements["achieved"])) + self.update_achievements_public_profile( + username, len(user_achievements["achieved"]) + ) if new_user: return True return False @@ -723,13 +833,19 @@ def add_commands_to_username(self, username, commands): ACHIEVEMENTS.put(user_achievements) def increase_user_run_count(self, username): - ACHIEVEMENTS.update({"username": username}, {"run_programs": dynamo.DynamoIncrement(1)}) + ACHIEVEMENTS.update( + {"username": username}, {"run_programs": dynamo.DynamoIncrement(1)} + ) def increase_user_save_count(self, username): - ACHIEVEMENTS.update({"username": username}, {"saved_programs": dynamo.DynamoIncrement(1)}) + ACHIEVEMENTS.update( + {"username": username}, {"saved_programs": dynamo.DynamoIncrement(1)} + ) def increase_user_submit_count(self, username): - ACHIEVEMENTS.update({"username": username}, {"submitted_programs": dynamo.DynamoIncrement(1)}) + ACHIEVEMENTS.update( + {"username": username}, {"submitted_programs": dynamo.DynamoIncrement(1)} + ) def update_public_profile(self, username, data): PUBLIC_PROFILES.update({"username": username}, data) @@ -739,7 +855,8 @@ def update_achievements_public_profile(self, username, amount_achievements): # In the case that we make this call but there is no public profile -> don't do anything if data: PUBLIC_PROFILES.update( - {"username": username}, {"achievements": amount_achievements, "last_achievement": timems()} + {"username": username}, + {"achievements": amount_achievements, "last_achievement": timems()}, ) def update_country_public_profile(self, username, country): @@ -793,7 +910,10 @@ def get_quiz_stats(self, ids, start=None, end=None): start_week = self.to_year_week(self.parse_date(start, date(2022, 1, 1))) end_week = self.to_year_week(self.parse_date(end, date.today())) - data = [QUIZ_STATS.get_many({"id": i, "week": dynamo.Between(start_week, end_week)}) for i in ids] + data = [ + QUIZ_STATS.get_many({"id": i, "week": dynamo.Between(start_week, end_week)}) + for i in ids + ] return functools.reduce(operator.iconcat, data, []) def add_program_stats(self, id, level, number_of_lines, exception): @@ -811,7 +931,12 @@ def get_program_stats(self, ids, start=None, end=None): start_week = self.to_year_week(self.parse_date(start, date(2022, 1, 1))) end_week = self.to_year_week(self.parse_date(end, date.today())) - data = [PROGRAM_STATS.get_many({"id": i, "week": dynamo.Between(start_week, end_week)}) for i in ids] + data = [ + PROGRAM_STATS.get_many( + {"id": i, "week": dynamo.Between(start_week, end_week)} + ) + for i in ids + ] return functools.reduce(operator.iconcat, data, []) def parse_date(self, d, default): @@ -822,5 +947,9 @@ def to_year_week(self, d): return f"{cal[0]}-{cal[1]:02d}" def get_username_role(self, username): - role = "teacher" if USERS.get({"username": username}).get("teacher_request") is True else "student" + role = ( + "teacher" + if USERS.get({"username": username}).get("teacher_request") is True + else "student" + ) return role diff --git a/website/dynamo.py b/website/dynamo.py index 7f8d933cf7d..5ebceb2dc85 100644 --- a/website/dynamo.py +++ b/website/dynamo.py @@ -36,8 +36,18 @@ def batch_get_item(self, table_name, keys_map, table_key_names): def query(self, table_name, key, sort_key, reverse, limit, pagination_token): ... - def query_index(self, table_name, index_name, keys, sort_key, reverse=False, - limit=None, pagination_token=None, keys_only=None, table_key_names=None): + def query_index( + self, + table_name, + index_name, + keys, + sort_key, + reverse=False, + limit=None, + pagination_token=None, + keys_only=None, + table_key_names=None, + ): ... def put(self, table_name, key, data): @@ -72,13 +82,21 @@ class Index: Specify if the index is a keys-only index. If not, is is expected to have all fields. """ - def __init__(self, partition_key: str, sort_key: str = None, index_name: str = None, keys_only: bool = False): + def __init__( + self, + partition_key: str, + sort_key: str = None, + index_name: str = None, + keys_only: bool = False, + ): self.partition_key = partition_key self.sort_key = sort_key self.index_name = index_name self.keys_only = keys_only if not self.index_name: - self.index_name = '-'.join([partition_key] + ([sort_key] if sort_key else [])) + '-index' + self.index_name = ( + "-".join([partition_key] + ([sort_key] if sort_key else [])) + "-index" + ) @dataclass @@ -118,6 +136,7 @@ def __nonzero__(self): class Cancel(metaclass=ABCMeta): """Contract for cancellation tokens.""" + @staticmethod def after_timeout(duration): return TimeoutCancellation(datetime.datetime.now() + duration) @@ -161,13 +180,22 @@ class Table: - sort_key: a field that is the sort key for the table. """ - def __init__(self, storage: TableStorage, table_name, partition_key, sort_key=None, indexes=None): + def __init__( + self, + storage: TableStorage, + table_name, + partition_key, + sort_key=None, + indexes=None, + ): self.storage = storage self.table_name = table_name self.partition_key = partition_key self.sort_key = sort_key self.indexes = indexes or [] - self.key_names = [self.partition_key] + ([self.sort_key] if self.sort_key else []) + self.key_names = [self.partition_key] + ( + [self.sort_key] if self.sort_key else [] + ) @querylog.timed_as("db_get") def get(self, key): @@ -183,8 +211,13 @@ def get(self, key): if isinstance(lookup, IndexLookup): return first_or_none( self.storage.query_index( - lookup.table_name, lookup.index_name, lookup.key, sort_key=lookup.sort_key, limit=1, - keys_only=lookup.keys_only, table_key_names=self.key_names, + lookup.table_name, + lookup.index_name, + lookup.key, + sort_key=lookup.sort_key, + limit=1, + keys_only=lookup.keys_only, + table_key_names=self.key_names, )[0] ) assert False @@ -206,21 +239,26 @@ def batch_get(self, keys): querylog.log_counter(f"db_batch_get:{self.table_name}") input_is_dict = isinstance(keys, dict) - keys_dict = keys if input_is_dict else {f'k{i}': k for i, k in enumerate(keys)} + keys_dict = keys if input_is_dict else {f"k{i}": k for i, k in enumerate(keys)} - lookups = {k: self._determine_lookup(key, many=False) for k, key in keys_dict.items()} + lookups = { + k: self._determine_lookup(key, many=False) for k, key in keys_dict.items() + } if any(not isinstance(lookup, TableLookup) for lookup in lookups.values()): - raise RuntimeError(f'batch_get must query table, not indexes, in: {keys}') + raise RuntimeError(f"batch_get must query table, not indexes, in: {keys}") if not lookups: return {} if input_is_dict else [] first_lookup = next(iter(lookups.values())) resp_dict = self.storage.batch_get_item( - first_lookup.table_name, {k: l.key for k, l in lookups.items()}, table_key_names=self.key_names) + first_lookup.table_name, + {k: l.key for k, l in lookups.items()}, + table_key_names=self.key_names, + ) if input_is_dict: return {k: resp_dict.get(k) for k in keys.keys()} else: - return [resp_dict.get(f'k{i}') for i in range(len(keys))] + return [resp_dict.get(f"k{i}") for i in range(len(keys))] @querylog.timed_as("db_get_many") def get_many(self, key, reverse=False, limit=None, pagination_token=None): @@ -272,9 +310,13 @@ def get_all(self, key, reverse=False): def create(self, data): """Put a single complete record into the database.""" if self.partition_key not in data: - raise ValueError(f"Expecting '{self.partition_key}' field in create() call, got: {data}") + raise ValueError( + f"Expecting '{self.partition_key}' field in create() call, got: {data}" + ) if self.sort_key and self.sort_key not in data: - raise ValueError(f"Expecting '{self.sort_key}' field in create() call, got: {data}") + raise ValueError( + f"Expecting '{self.sort_key}' field in create() call, got: {data}" + ) querylog.log_counter(f"db_create:{self.table_name}") self.storage.put(self.table_name, self._extract_key(data), data) @@ -333,7 +375,9 @@ def scan(self, limit=None, pagination_token=None): """Reads the entire table into memory.""" querylog.log_counter("db_scan:" + self.table_name) items, next_page_token = self.storage.scan( - self.table_name, limit=limit, pagination_token=decode_page_token(pagination_token) + self.table_name, + limit=limit, + pagination_token=decode_page_token(pagination_token), ) return ResultPage(items, encode_page_token(next_page_token)) @@ -356,19 +400,30 @@ def _determine_lookup(self, key_data, many): # We do an index table lookup if the partition (and possibly the sort key) of an index occur in the given key. for index in self.indexes: - index_key_names = [x for x in [index.partition_key, index.sort_key] if x is not None] + index_key_names = [ + x for x in [index.partition_key, index.sort_key] if x is not None + ] if keys == set(index_key_names) or one_key == index.partition_key: - return IndexLookup(self.table_name, index.index_name, key_data, - index.sort_key, keys_only=index.keys_only) + return IndexLookup( + self.table_name, + index.index_name, + key_data, + index.sort_key, + keys_only=index.keys_only, + ) if len(keys) != 1: - raise RuntimeError(f"Getting key data: {key_data}, but expecting: {table_keys}") + raise RuntimeError( + f"Getting key data: {key_data}, but expecting: {table_keys}" + ) # If the one key matches the partition key, it must be because we also have a # sort key, but that's allowed because we are looking for 'many' records. if one_key == self.partition_key: if not many: - raise RuntimeError(f"Looking up one value, but missing sort key: {self.sort_key} in {key_data}") + raise RuntimeError( + f"Looking up one value, but missing sort key: {self.sort_key} in {key_data}" + ) return TableLookup(self.table_name, key_data) raise RuntimeError(f"Field not partition key or index: {one_key}") @@ -378,7 +433,9 @@ def _extract_key(self, data): Extract the key data out of plain data. """ if self.partition_key not in data: - raise RuntimeError(f"Partition key '{self.partition_key}' missing from data: {data}") + raise RuntimeError( + f"Partition key '{self.partition_key}' missing from data: {data}" + ) if self.sort_key and self.sort_key not in data: raise RuntimeError(f"Sort key '{self.sort_key}' missing from data: {data}") @@ -413,7 +470,9 @@ def __init__(self, db, db_prefix): self.db_prefix = db_prefix def get_item(self, table_name, key): - result = self.db.get_item(TableName=make_table_name(self.db_prefix, table_name), Key=self._encode(key)) + result = self.db.get_item( + TableName=make_table_name(self.db_prefix, table_name), Key=self._encode(key) + ) return self._decode(result.get("Item", None)) def batch_get_item(self, table_name, keys_map, table_key_names): @@ -446,15 +505,19 @@ def fill_er_up(): backoff = ExponentialBackoff() while next_query: result = self.db.batch_get_item( - RequestItems={real_table_name: {'Keys': next_query}} + RequestItems={real_table_name: {"Keys": next_query}} ) - for row in result.get('Responses', {}).get(real_table_name, []): + for row in result.get("Responses", {}).get(real_table_name, []): record = self._decode(row) for id in key_to_ids[immutable_key(record)]: ret[id] = record # The DB may not have done everything (we might have gotten throttled). If so, sleep. - next_query = result.get('UnprocessedKeys', {}).get(real_table_name, {}).get('Keys', []) + next_query = ( + result.get("UnprocessedKeys", {}) + .get(real_table_name, {}) + .get("Keys", []) + ) backoff.sleep_when(to_query) fill_er_up() @@ -470,7 +533,9 @@ def query(self, table_name, key, sort_key, reverse, limit, pagination_token): ScanIndexForward=not reverse, ExpressionAttributeNames=attr_names, Limit=limit, - ExclusiveStartKey=self._encode(pagination_token) if pagination_token else None, + ExclusiveStartKey=self._encode(pagination_token) + if pagination_token + else None, ) ) @@ -478,8 +543,18 @@ def query(self, table_name, key, sort_key, reverse, limit, pagination_token): next_page_token = self._decode(result.get("LastEvaluatedKey", None)) return items, next_page_token - def query_index(self, table_name, index_name, keys, sort_key, reverse=False, limit=None, pagination_token=None, - keys_only=None, table_key_names=None): + def query_index( + self, + table_name, + index_name, + keys, + sort_key, + reverse=False, + limit=None, + pagination_token=None, + keys_only=None, + table_key_names=None, + ): # keys_only is ignored here -- that's only necessary for the in-memory implementation. # In an actual DDB table, that's an attribute of the index itself @@ -494,13 +569,17 @@ def query_index(self, table_name, index_name, keys, sort_key, reverse=False, lim ScanIndexForward=not reverse, ExpressionAttributeNames=attr_names, Limit=limit, - ExclusiveStartKey=self._encode(pagination_token) if pagination_token else None, + ExclusiveStartKey=self._encode(pagination_token) + if pagination_token + else None, ) ) items = [self._decode(x) for x in result.get("Items", [])] next_page_token = ( - self._decode(result.get("LastEvaluatedKey", None)) if result.get("LastEvaluatedKey", None) else None + self._decode(result.get("LastEvaluatedKey", None)) + if result.get("LastEvaluatedKey", None) + else None ) return items, next_page_token @@ -515,10 +594,16 @@ def _prep_query_data(self, key, sort_key=None): key_expression = " AND ".join( [f"#{field} = :{field}" for field in eq_conditions.keys()] - + [cond.to_dynamo_expression(field) for field, cond in special_conditions.items()] + + [ + cond.to_dynamo_expression(field) + for field, cond in special_conditions.items() + ] ) - attr_values = {f":{field}": DDB_SERIALIZER.serialize(key[field]) for field in eq_conditions.keys()} + attr_values = { + f":{field}": DDB_SERIALIZER.serialize(key[field]) + for field in eq_conditions.keys() + } for field, cond in special_conditions.items(): attr_values.update(cond.to_dynamo_values(field)) @@ -527,11 +612,18 @@ def _prep_query_data(self, key, sort_key=None): return key_expression, attr_values, attr_names def put(self, table_name, _key, data): - self.db.put_item(TableName=make_table_name(self.db_prefix, table_name), Item=self._encode(data)) + self.db.put_item( + TableName=make_table_name(self.db_prefix, table_name), + Item=self._encode(data), + ) def update(self, table_name, key, updates): - value_updates = {k: v for k, v in updates.items() if not isinstance(v, DynamoUpdate)} - special_updates = {k: v.to_dynamo() for k, v in updates.items() if isinstance(v, DynamoUpdate)} + value_updates = { + k: v for k, v in updates.items() if not isinstance(v, DynamoUpdate) + } + special_updates = { + k: v.to_dynamo() for k, v in updates.items() if isinstance(v, DynamoUpdate) + } response = self.db.update_item( TableName=make_table_name(self.db_prefix, table_name), @@ -541,15 +633,19 @@ def update(self, table_name, key, updates): **special_updates, }, # Return the full new item after update - ReturnValues='ALL_NEW', + ReturnValues="ALL_NEW", ) - return self._decode(response.get('Attributes', {})) + return self._decode(response.get("Attributes", {})) def delete(self, table_name, key): - return self.db.delete_item(TableName=make_table_name(self.db_prefix, table_name), Key=self._encode(key)) + return self.db.delete_item( + TableName=make_table_name(self.db_prefix, table_name), Key=self._encode(key) + ) def item_count(self, table_name): - result = self.db.describe_table(TableName=make_table_name(self.db_prefix, table_name)) + result = self.db.describe_table( + TableName=make_table_name(self.db_prefix, table_name) + ) return result["Table"]["ItemCount"] def scan(self, table_name, limit, pagination_token): @@ -557,12 +653,16 @@ def scan(self, table_name, limit, pagination_token): **notnone( TableName=make_table_name(self.db_prefix, table_name), Limit=limit, - ExclusiveStartKey=self._encode(pagination_token) if pagination_token else None, + ExclusiveStartKey=self._encode(pagination_token) + if pagination_token + else None, ) ) items = [self._decode(x) for x in result.get("Items", [])] next_page_token = ( - self._decode(result.get("LastEvaluatedKey", None)) if result.get("LastEvaluatedKey", None) else None + self._decode(result.get("LastEvaluatedKey", None)) + if result.get("LastEvaluatedKey", None) + else None ) return items, next_page_token @@ -583,7 +683,10 @@ def _decode(self, data): if data is None: return None - return {k: replace_decimals(DDB_DESERIALIZER.deserialize(v)) for k, v in data.items()} + return { + k: replace_decimals(DDB_DESERIALIZER.deserialize(v)) + for k, v in data.items() + } class Lock: @@ -626,7 +729,14 @@ def __init__(self, filename=None): # NOTE: on purpose not @synchronized here def get_item(self, table_name, key): - items, _ = self.query(table_name, key, sort_key=None, reverse=False, limit=None, pagination_token=None) + items, _ = self.query( + table_name, + key, + sort_key=None, + reverse=False, + limit=None, + pagination_token=None, + ) return first_or_none(items) def batch_get_item(self, table_name, keys_map, table_key_names): @@ -639,7 +749,11 @@ def query(self, table_name, key, sort_key, reverse, limit, pagination_token): validate_only_sort_key(special_conditions, sort_key) records = self.tables.get(table_name, []) - filtered = [r for r in records if self._query_matches(r, eq_conditions, special_conditions)] + filtered = [ + r + for r in records + if self._query_matches(r, eq_conditions, special_conditions) + ] if sort_key: filtered.sort(key=lambda x: x[sort_key]) @@ -667,7 +781,11 @@ def before_or_equal(key0, key1): return k0 <= k1 if not reverse or not sort_key else k1 <= k0 with_keys = [(extract_key(i, r), r) for i, r in enumerate(filtered)] - while pagination_token and with_keys and before_or_equal(with_keys[0][0], pagination_token): + while ( + pagination_token + and with_keys + and before_or_equal(with_keys[0][0], pagination_token) + ): with_keys.pop(0) next_page_key = None @@ -678,22 +796,41 @@ def before_or_equal(key0, key1): return copy.copy([record for _, record in with_keys]), next_page_key # NOTE: on purpose not @synchronized here - def query_index(self, table_name, index_name, keys, sort_key, reverse=False, limit=None, pagination_token=None, - keys_only=None, table_key_names=None): + def query_index( + self, + table_name, + index_name, + keys, + sort_key, + reverse=False, + limit=None, + pagination_token=None, + keys_only=None, + table_key_names=None, + ): # If keys_only, we project down to the index + table keys # In a REAL dynamo table, the index just wouldn't have more data. The in-memory table has everything, # so we need to drop some data so programmers don't accidentally rely on it. records, next_page_token = self.query( - table_name, keys, sort_key=sort_key, reverse=reverse, limit=limit, pagination_token=pagination_token + table_name, + keys, + sort_key=sort_key, + reverse=reverse, + limit=limit, + pagination_token=pagination_token, ) if not keys_only: return records, next_page_token # In a keys_only index, we retain all fields that are in either a table or index key - keys_to_retain = set(list(keys.keys()) + ([sort_key] if sort_key else []) + table_key_names) - return [{key: record[key] for key in keys_to_retain} for record in records], next_page_token + keys_to_retain = set( + list(keys.keys()) + ([sort_key] if sort_key else []) + table_key_names + ) + return [ + {key: record[key] for key in keys_to_retain} for record in records + ], next_page_token @lock.synchronized def put(self, table_name, key, data): @@ -739,7 +876,9 @@ def update(self, table_name, key, updates): raise TypeError(f"Expected a set in {name}, got: {existing}") record[name] = existing | set(update.elements) else: - raise RuntimeError(f"Unsupported update type for in-memory database: {update}") + raise RuntimeError( + f"Unsupported update type for in-memory database: {update}" + ) elif update is None: if name in record: del record[name] @@ -771,7 +910,7 @@ def scan(self, table_name, limit, pagination_token): start_index = 0 if pagination_token: start_index = pagination_token["offset"] - items = items[pagination_token["offset"]:] + items = items[pagination_token["offset"] :] next_page_token = None if limit and limit < len(items): @@ -922,8 +1061,12 @@ def partition(key): NOT of type DynamoCondition. The other one will contain all the elements for which the value ARE DynamoConditions. """ - eq_conditions = {k: v for k, v in key.items() if not isinstance(v, DynamoCondition)} - special_conditions = {k: v for k, v in key.items() if isinstance(v, DynamoCondition)} + eq_conditions = { + k: v for k, v in key.items() if not isinstance(v, DynamoCondition) + } + special_conditions = { + k: v for k, v in key.items() if isinstance(v, DynamoCondition) + } return (eq_conditions, special_conditions) @@ -993,7 +1136,9 @@ def decode_object(obj): def validate_only_sort_key(conds, sort_key): """Check that only the sort key is used in the given key conditions.""" if sort_key and set(conds.keys()) - {sort_key}: - raise RuntimeError(f"Conditions only allowed on sort key {sort_key}, got: {list(conds)}") + raise RuntimeError( + f"Conditions only allowed on sort key {sort_key}, got: {list(conds)}" + ) def encode_page_token(x): @@ -1022,7 +1167,7 @@ class ExponentialBackoff: def __init__(self): self.time = 0.05 - @querylog.timed_as('db:sleep') + @querylog.timed_as("db:sleep") def sleep(self): time.sleep(random.randint(0, self.time)) self.time *= 2 @@ -1055,12 +1200,12 @@ def _fetch_next_page(self): self.i = 0 self.page = self._do_fetch() if not isinstance(self.page, ResultPage): - raise RuntimeError('_do_fetch must return a ResultPage') + raise RuntimeError("_do_fetch must return a ResultPage") self.have_eof = len(self.page) == 0 def _do_fetch(self): - raise NotImplementedError('_do_fetch should be implemented') + raise NotImplementedError("_do_fetch should be implemented") @property def eof(self): @@ -1094,7 +1239,7 @@ def current(self): if self.page is None: self._fetch_next_page() if self.eof: - raise RuntimeError('At eof') + raise RuntimeError("At eof") return self.page[self.i] def __iter__(self): @@ -1114,7 +1259,7 @@ def _analyze_pagination_token(self, x): self.pagination_token = None return 0 - parts = x.split('@') + parts = x.split("@") self.pagination_token = parts[0] or None return int(parts[1]) @@ -1155,10 +1300,12 @@ def __init__(self, table, key, reverse=False, limit=None, pagination_token=None) super().__init__(pagination_token) def _do_fetch(self): - return self.table.get_many(self.key, - reverse=self.reverse, - limit=self.limit, - pagination_token=self.pagination_token) + return self.table.get_many( + self.key, + reverse=self.reverse, + limit=self.limit, + pagination_token=self.pagination_token, + ) class ScanIterator(QueryIterator): diff --git a/website/flask_helpers.py b/website/flask_helpers.py index 585e75d157e..3a272737b46 100644 --- a/website/flask_helpers.py +++ b/website/flask_helpers.py @@ -42,7 +42,9 @@ def default(self, o): def strip_nones(x): if isinstance(x, dict): - return {k: v for k, v in x.items() if v is not None and not isinstance(v, Undefined)} + return { + k: v for k, v in x.items() if v is not None and not isinstance(v, Undefined) + } return x diff --git a/website/for_teachers.py b/website/for_teachers.py index f4a9a27851a..4bb04ef2852 100644 --- a/website/for_teachers.py +++ b/website/for_teachers.py @@ -58,7 +58,9 @@ def for_teachers_page(self, user): } ) - keyword_language = request.args.get('keyword_language', default=g.keyword_lang, type=str) + keyword_language = request.args.get( + "keyword_language", default=g.keyword_lang, type=str + ) slides = [] for level in range(hedy.HEDY_MAX_LEVEL + 1): if SLIDES[g.lang].get_slides_for_level(level, keyword_language): @@ -73,54 +75,62 @@ def for_teachers_page(self, user): welcome_teacher=welcome_teacher, slides=slides, javascript_page_options=dict( - page='for-teachers', + page="for-teachers", welcome_teacher=welcome_teacher, - )) + ), + ) - @route("/manual", methods=["GET"], defaults={'section_key': 'intro'}) + @route("/manual", methods=["GET"], defaults={"section_key": "intro"}) @route("/manual/", methods=["GET"]) def get_teacher_manual(self, section_key): content = hedyweb.PageTranslations("for-teachers").get_page_translations(g.lang) # Code very defensively around types here -- Weblate has a tendency to mess up the YAML, # so the structure cannot be trusted. - page_title = content.get('title', '') - sections = {section['key']: section for section in content['sections']} - section_titles = [(section['key'], section.get('title', '')) for section in content['sections']] + page_title = content.get("title", "") + sections = {section["key"]: section for section in content["sections"]} + section_titles = [ + (section["key"], section.get("title", "")) + for section in content["sections"] + ] current_section = sections.get(section_key) if not current_section: return utils.error_page(error=404, ui_message=gettext("page_not_found")) - intro = current_section.get('intro') + intro = current_section.get("intro") # Some pages have 'subsections', others have 'levels'. We're going to treat them ~the same. # Give levels a 'title' field as well (doesn't have it in the YAML). - subsections = current_section.get('subsections', []) + subsections = current_section.get("subsections", []) for subsection in subsections: - subsection.setdefault('title', '') - levels = current_section.get('levels', []) + subsection.setdefault("title", "") + levels = current_section.get("levels", []) for level in levels: - level['title'] = gettext('level') + ' ' + str(level['level']) + level["title"] = gettext("level") + " " + str(level["level"]) - subsection_titles = [x.get('title', '') for x in subsections + levels] + subsection_titles = [x.get("title", "") for x in subsections + levels] - return render_template("teacher-manual.html", - current_page="teacher-manual", - page_title=page_title, - section_titles=section_titles, - section_key=section_key, - section_title=current_section['title'], - intro=intro, - subsection_titles=subsection_titles, - subsections=subsections, - levels=levels) + return render_template( + "teacher-manual.html", + current_page="teacher-manual", + page_title=page_title, + section_titles=section_titles, + section_key=section_key, + section_title=current_section["title"], + intro=intro, + subsection_titles=subsection_titles, + subsections=subsections, + levels=levels, + ) @route("/class/", methods=["GET"]) @requires_login def get_class(self, user, class_id): if not is_teacher(user) and not is_admin(user): - return utils.error_page(error=403, ui_message=gettext("retrieve_class_error")) + return utils.error_page( + error=403, ui_message=gettext("retrieve_class_error") + ) Class = self.db.get_class(class_id) if not Class or (Class["teacher"] != user["username"] and not is_admin(user)): return utils.error_page(error=404, ui_message=gettext("no_such_class")) @@ -133,7 +143,11 @@ def get_class(self, user, class_id): quiz_scores = self.db.get_quiz_stats([student_username]) # Verify if the user did finish any quiz before getting the max() of the finished levels finished_quizzes = any("finished" in x for x in quiz_scores) - highest_quiz = max([x.get("level") for x in quiz_scores if x.get("finished")]) if finished_quizzes else "-" + highest_quiz = ( + max([x.get("level") for x in quiz_scores if x.get("finished")]) + if finished_quizzes + else "-" + ) students.append( { "username": student_username, @@ -147,14 +161,25 @@ def get_class(self, user, class_id): students = sorted(students, key=lambda d: d.get("last_login", 0), reverse=True) # After sorting: replace the number value by a string format date for student in students: - student["last_login"] = utils.localized_date_format(student.get("last_login", 0)) + student["last_login"] = utils.localized_date_format( + student.get("last_login", 0) + ) if utils.is_testing_request(request): - return jsonify({"students": students, "link": Class["link"], "name": Class["name"], "id": Class["id"]}) + return jsonify( + { + "students": students, + "link": Class["link"], + "name": Class["name"], + "id": Class["id"], + } + ) achievement = None if len(students) > 20: - achievement = self.achievements.add_single_achievement(user["username"], "full_house") + achievement = self.achievements.add_single_achievement( + user["username"], "full_house" + ) if achievement: achievement = json.dumps(achievement) @@ -163,8 +188,12 @@ def get_class(self, user, class_id): invites.append( { "username": invite["username"], - "timestamp": utils.localized_date_format(invite["timestamp"], short_format=True), - "expire_timestamp": utils.localized_date_format(invite["ttl"], short_format=True), + "timestamp": utils.localized_date_format( + invite["timestamp"], short_format=True + ), + "expire_timestamp": utils.localized_date_format( + invite["ttl"], short_format=True + ), } ) @@ -188,19 +217,30 @@ def get_class(self, user, class_id): @requires_login def get_class_customization_page(self, user, class_id): if not is_teacher(user) and not is_admin(user): - return utils.error_page(error=403, ui_message=gettext("retrieve_class_error")) + return utils.error_page( + error=403, ui_message=gettext("retrieve_class_error") + ) Class = self.db.get_class(class_id) if not Class or (Class["teacher"] != user["username"] and not is_admin(user)): return utils.error_page(error=404, ui_message=gettext("no_such_class")) - session['class_id'] = class_id - customizations, adventures, adventure_names, available_adventures, min_level = \ - self.get_class_info(user, class_id) + session["class_id"] = class_id + ( + customizations, + adventures, + adventure_names, + available_adventures, + min_level, + ) = self.get_class_info(user, class_id) return render_template( "customize-class.html", page_title=gettext("title_customize-class"), - class_info={"name": Class["name"], "id": Class["id"], "teacher": Class["teacher"]}, + class_info={ + "name": Class["name"], + "id": Class["id"], + "teacher": Class["teacher"], + }, max_level=hedy.HEDY_MAX_LEVEL, customizations=customizations, adventures=adventures, @@ -210,182 +250,256 @@ def get_class_customization_page(self, user, class_id): current_page="for-teachers", min_level=min_level, class_id=class_id, - javascript_page_options=dict( - page='customize-class', - class_id=class_id - )) + javascript_page_options=dict(page="customize-class", class_id=class_id), + ) @route("/get-customization-level", methods=["GET"]) @requires_login def change_dropdown_level(self, user): if not is_teacher(user) and not is_admin(user): - return utils.error_page(error=403, ui_message=gettext("retrieve_class_error")) - Class = self.db.get_class(session['class_id']) + return utils.error_page( + error=403, ui_message=gettext("retrieve_class_error") + ) + Class = self.db.get_class(session["class_id"]) if not Class or (Class["teacher"] != user["username"] and not is_admin(user)): return utils.error_page(error=404, ui_message=gettext("no_such_class")) - level = request.args.get('level') - customizations, adventures, adventure_names, available_adventures, _ = self.get_class_info( - user, session['class_id']) - - return render_partial('customize-class/partial-sortable-adventures.html', - level=level, - customizations=customizations, - adventures=adventures, - max_level=hedy.HEDY_MAX_LEVEL, - adventure_names=adventure_names, - adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, - available_adventures=available_adventures, - class_id=session['class_id']) + level = request.args.get("level") + ( + customizations, + adventures, + adventure_names, + available_adventures, + _, + ) = self.get_class_info(user, session["class_id"]) + + return render_partial( + "customize-class/partial-sortable-adventures.html", + level=level, + customizations=customizations, + adventures=adventures, + max_level=hedy.HEDY_MAX_LEVEL, + adventure_names=adventure_names, + adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, + available_adventures=available_adventures, + class_id=session["class_id"], + ) @route("/add-adventure/level/", methods=["POST"]) @requires_login def add_adventure(self, user, level): if not is_teacher(user) and not is_admin(user): - return utils.error_page(error=403, ui_message=gettext("retrieve_class_error")) - Class = self.db.get_class(session['class_id']) + return utils.error_page( + error=403, ui_message=gettext("retrieve_class_error") + ) + Class = self.db.get_class(session["class_id"]) if not Class or (Class["teacher"] != user["username"] and not is_admin(user)): return utils.error_page(error=404, ui_message=gettext("no_such_class")) - adventure_id = request.form.get('adventure_id') - customizations, adventures, adventure_names, available_adventures, _ = self.get_class_info( - user, session['class_id']) + adventure_id = request.form.get("adventure_id") + ( + customizations, + adventures, + adventure_names, + available_adventures, + _, + ) = self.get_class_info(user, session["class_id"]) teacher_adventures = self.db.get_teacher_adventures(user["username"]) - is_teacher_adventure = self.is_adventure_from_teacher(adventure_id, teacher_adventures) + is_teacher_adventure = self.is_adventure_from_teacher( + adventure_id, teacher_adventures + ) - customizations['sorted_adventures'][level].append({'name': adventure_id, 'from_teacher': is_teacher_adventure}) - sorted_adventure = SortedAdventure(short_name=adventure_id, - long_name=adventure_names[adventure_id], - is_teacher_adventure=is_teacher_adventure, - is_command_adventure=adventure_id in hedy_content.KEYWORDS_ADVENTURES) + customizations["sorted_adventures"][level].append( + {"name": adventure_id, "from_teacher": is_teacher_adventure} + ) + sorted_adventure = SortedAdventure( + short_name=adventure_id, + long_name=adventure_names[adventure_id], + is_teacher_adventure=is_teacher_adventure, + is_command_adventure=adventure_id in hedy_content.KEYWORDS_ADVENTURES, + ) adventures[int(level)].append(sorted_adventure) self.db.update_class_customizations(customizations) - available_adventures = self.get_unused_adventures(adventures, teacher_adventures, adventure_names) - - return render_partial('customize-class/partial-sortable-adventures.html', - level=level, - customizations=customizations, - adventures=adventures, - max_level=hedy.HEDY_MAX_LEVEL, - adventure_names=adventure_names, - adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, - available_adventures=available_adventures, - class_id=session['class_id']) + available_adventures = self.get_unused_adventures( + adventures, teacher_adventures, adventure_names + ) + + return render_partial( + "customize-class/partial-sortable-adventures.html", + level=level, + customizations=customizations, + adventures=adventures, + max_level=hedy.HEDY_MAX_LEVEL, + adventure_names=adventure_names, + adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, + available_adventures=available_adventures, + class_id=session["class_id"], + ) @route("/remove-adventure", methods=["POST"]) @requires_login def remove_adventure_from_class(self, user): if not is_teacher(user) and not is_admin(user): - return utils.error_page(error=403, ui_message=gettext("retrieve_class_error")) - Class = self.db.get_class(session['class_id']) + return utils.error_page( + error=403, ui_message=gettext("retrieve_class_error") + ) + Class = self.db.get_class(session["class_id"]) if not Class or (Class["teacher"] != user["username"] and not is_admin(user)): return utils.error_page(error=404, ui_message=gettext("no_such_class")) - adventure_id = request.args.get('adventure_id') - level = request.args.get('level') + adventure_id = request.args.get("adventure_id") + level = request.args.get("level") teacher_adventures = self.db.get_teacher_adventures(user["username"]) - is_teacher_adventure = self.is_adventure_from_teacher(adventure_id, teacher_adventures) - customizations, adventures, adventure_names, available_adventures, _ = self.get_class_info( - user, session['class_id']) - customizations['sorted_adventures'][level].remove({'name': adventure_id, 'from_teacher': is_teacher_adventure}) - sorted_adventure = SortedAdventure(short_name=adventure_id, - long_name=adventure_names[adventure_id], - is_teacher_adventure=is_teacher_adventure, - is_command_adventure=adventure_id in hedy_content.KEYWORDS_ADVENTURES) + is_teacher_adventure = self.is_adventure_from_teacher( + adventure_id, teacher_adventures + ) + ( + customizations, + adventures, + adventure_names, + available_adventures, + _, + ) = self.get_class_info(user, session["class_id"]) + customizations["sorted_adventures"][level].remove( + {"name": adventure_id, "from_teacher": is_teacher_adventure} + ) + sorted_adventure = SortedAdventure( + short_name=adventure_id, + long_name=adventure_names[adventure_id], + is_teacher_adventure=is_teacher_adventure, + is_command_adventure=adventure_id in hedy_content.KEYWORDS_ADVENTURES, + ) adventures[int(level)].remove(sorted_adventure) self.db.update_class_customizations(customizations) - available_adventures = self.get_unused_adventures(adventures, teacher_adventures, adventure_names) - - return render_partial('customize-class/partial-sortable-adventures.html', - level=level, - customizations=customizations, - adventures=adventures, - max_level=hedy.HEDY_MAX_LEVEL, - adventure_names=adventure_names, - adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, - available_adventures=available_adventures, - class_id=session['class_id']) + available_adventures = self.get_unused_adventures( + adventures, teacher_adventures, adventure_names + ) + + return render_partial( + "customize-class/partial-sortable-adventures.html", + level=level, + customizations=customizations, + adventures=adventures, + max_level=hedy.HEDY_MAX_LEVEL, + adventure_names=adventure_names, + adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, + available_adventures=available_adventures, + class_id=session["class_id"], + ) @route("/sort-adventures", methods=["POST"]) @requires_login def sort_adventures_in_class(self, user): if not is_teacher(user) and not is_admin(user): - return utils.error_page(error=403, ui_message=gettext("retrieve_class_error")) - Class = self.db.get_class(session['class_id']) + return utils.error_page( + error=403, ui_message=gettext("retrieve_class_error") + ) + Class = self.db.get_class(session["class_id"]) if not Class or (Class["teacher"] != user["username"] and not is_admin(user)): return utils.error_page(error=404, ui_message=gettext("no_such_class")) - customizations, adventures, adventure_names, available_adventures, _ = self.get_class_info( - user, session['class_id']) - level = request.args.get('level') - adventures_from_request = request.form.getlist('adventure') + ( + customizations, + adventures, + adventure_names, + available_adventures, + _, + ) = self.get_class_info(user, session["class_id"]) + level = request.args.get("level") + adventures_from_request = request.form.getlist("adventure") teacher_adventures = self.db.get_teacher_adventures(user["username"]) - customizations['sorted_adventures'][level] = [] + customizations["sorted_adventures"][level] = [] adventures[int(level)] = [] for adventure in adventures_from_request: - is_teacher_adventure = self.is_adventure_from_teacher(adventure, teacher_adventures) - customizations['sorted_adventures'][level].append({'name': adventure, 'from_teacher': is_teacher_adventure}) - sorted_adventure = SortedAdventure(short_name=adventure, - long_name=adventure_names[adventure], - is_teacher_adventure=is_teacher_adventure, - is_command_adventure=adventure in hedy_content.KEYWORDS_ADVENTURES) + is_teacher_adventure = self.is_adventure_from_teacher( + adventure, teacher_adventures + ) + customizations["sorted_adventures"][level].append( + {"name": adventure, "from_teacher": is_teacher_adventure} + ) + sorted_adventure = SortedAdventure( + short_name=adventure, + long_name=adventure_names[adventure], + is_teacher_adventure=is_teacher_adventure, + is_command_adventure=adventure in hedy_content.KEYWORDS_ADVENTURES, + ) adventures[int(level)].append(sorted_adventure) self.db.update_class_customizations(customizations) - return render_partial('customize-class/partial-sortable-adventures.html', - level=level, - customizations=customizations, - adventures=adventures, - max_level=hedy.HEDY_MAX_LEVEL, - adventure_names=adventure_names, - adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, - available_adventures=available_adventures, - class_id=session['class_id']) + return render_partial( + "customize-class/partial-sortable-adventures.html", + level=level, + customizations=customizations, + adventures=adventures, + max_level=hedy.HEDY_MAX_LEVEL, + adventure_names=adventure_names, + adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, + available_adventures=available_adventures, + class_id=session["class_id"], + ) def get_class_info(self, user, class_id): if hedy_content.Adventures(g.lang).has_adventures(): - default_adventures = hedy_content.Adventures(g.lang).get_adventure_keyname_name_levels() + default_adventures = hedy_content.Adventures( + g.lang + ).get_adventure_keyname_name_levels() else: - default_adventures = hedy_content.Adventures("en").get_adventure_keyname_name_levels() + default_adventures = hedy_content.Adventures( + "en" + ).get_adventure_keyname_name_levels() teacher_adventures = self.db.get_teacher_adventures(user["username"]) customizations = self.db.get_class_customizations(class_id) # Initialize the data structures that will hold the adventures for each level - adventures = {i: [] for i in range(1, hedy.HEDY_MAX_LEVEL+1)} + adventures = {i: [] for i in range(1, hedy.HEDY_MAX_LEVEL + 1)} min_level = 1 adventure_names = {} for adv_key, adv_dic in default_adventures.items(): for name, _ in adv_dic.items(): - adventure_names[adv_key] = hedy_content.get_localized_name(name, g.keyword_lang) + adventure_names[adv_key] = hedy_content.get_localized_name( + name, g.keyword_lang + ) for adventure in teacher_adventures: - adventure_names[adventure['id']] = adventure['name'] + adventure_names[adventure["id"]] = adventure["name"] if customizations: # in case this class has thew new way to select adventures - if 'sorted_adventures' in customizations: + if "sorted_adventures" in customizations: # remove from customizations adventures that we have removed - self.purge_customizations(customizations['sorted_adventures'], default_adventures) + self.purge_customizations( + customizations["sorted_adventures"], default_adventures + ) # it uses the old way so convert it to the new one - elif 'adventures' in customizations: - customizations['sorted_adventures'] = {str(i): [] for i in range(1, hedy.HEDY_MAX_LEVEL + 1)} - for adventure, levels in customizations['adventures'].items(): + elif "adventures" in customizations: + customizations["sorted_adventures"] = { + str(i): [] for i in range(1, hedy.HEDY_MAX_LEVEL + 1) + } + for adventure, levels in customizations["adventures"].items(): for level in levels: - customizations['sorted_adventures'][str(level)].append( - {"name": adventure, "from_teacher": False}) + customizations["sorted_adventures"][str(level)].append( + {"name": adventure, "from_teacher": False} + ) self.db.update_class_customizations(customizations) - min_level = 1 if customizations['levels'] == [] else min(customizations['levels']) + min_level = ( + 1 if customizations["levels"] == [] else min(customizations["levels"]) + ) else: # Since it doesn't have customizations loaded, we create a default customization object. # This makes further updating with HTMX easier adventures_to_db = {} - for level, default_adventures in hedy_content.ADVENTURE_ORDER_PER_LEVEL.items(): - adventures_to_db[str(level)] = [{'name': adventure, 'from_teacher': False} - for adventure in default_adventures] + for ( + level, + default_adventures, + ) in hedy_content.ADVENTURE_ORDER_PER_LEVEL.items(): + adventures_to_db[str(level)] = [ + {"name": adventure, "from_teacher": False} + for adventure in default_adventures + ] customizations = { "id": class_id, @@ -393,23 +507,33 @@ def get_class_info(self, user, class_id): "opening_dates": {}, "other_settings": [], "level_thresholds": {}, - "sorted_adventures": adventures_to_db + "sorted_adventures": adventures_to_db, } self.db.update_class_customizations(customizations) - for level, sorted_adventures in customizations['sorted_adventures'].items(): + for level, sorted_adventures in customizations["sorted_adventures"].items(): for adventure in sorted_adventures: - if adventure['name'] in adventure_names: - sorted_adventure = SortedAdventure(short_name=adventure['name'], - long_name=adventure_names[adventure['name']], - is_command_adventure=adventure['name'] - in hedy_content.KEYWORDS_ADVENTURES, - is_teacher_adventure=adventure['from_teacher']) + if adventure["name"] in adventure_names: + sorted_adventure = SortedAdventure( + short_name=adventure["name"], + long_name=adventure_names[adventure["name"]], + is_command_adventure=adventure["name"] + in hedy_content.KEYWORDS_ADVENTURES, + is_teacher_adventure=adventure["from_teacher"], + ) adventures[int(level)].append(sorted_adventure) - available_adventures = self.get_unused_adventures(adventures, teacher_adventures, adventure_names) + available_adventures = self.get_unused_adventures( + adventures, teacher_adventures, adventure_names + ) - return customizations, adventures, adventure_names, available_adventures, min_level + return ( + customizations, + adventures, + adventure_names, + available_adventures, + min_level, + ) # This function is used to remove from the customizations default adventures that we have removed # Otherwise they might cause an error when the students try to access a level @@ -417,34 +541,50 @@ def get_class_info(self, user, class_id): def purge_customizations(self, sorted_adventures, adventures): for _, adventure_list in sorted_adventures.items(): for adventure in list(adventure_list): - if not adventure['from_teacher'] and adventure['name'] not in adventures: + if ( + not adventure["from_teacher"] + and adventure["name"] not in adventures + ): adventure_list.remove(adventure) def get_unused_adventures(self, adventures, teacher_adventures_db, adventure_names): - available_adventures = {i: [] for i in range(1, hedy.HEDY_MAX_LEVEL+1)} - default_adventures = {i: set() for i in range(1, hedy.HEDY_MAX_LEVEL+1)} - teacher_adventures = {i: set() for i in range(1, hedy.HEDY_MAX_LEVEL+1)} - - for level, level_default_adventures in hedy_content.ADVENTURE_ORDER_PER_LEVEL.items(): + available_adventures = {i: [] for i in range(1, hedy.HEDY_MAX_LEVEL + 1)} + default_adventures = {i: set() for i in range(1, hedy.HEDY_MAX_LEVEL + 1)} + teacher_adventures = {i: set() for i in range(1, hedy.HEDY_MAX_LEVEL + 1)} + + for ( + level, + level_default_adventures, + ) in hedy_content.ADVENTURE_ORDER_PER_LEVEL.items(): for short_name in level_default_adventures: - adventure = SortedAdventure(short_name=short_name, - long_name=adventure_names[short_name], - is_teacher_adventure=False, - is_command_adventure=short_name in hedy_content.KEYWORDS_ADVENTURES) + adventure = SortedAdventure( + short_name=short_name, + long_name=adventure_names[short_name], + is_teacher_adventure=False, + is_command_adventure=short_name in hedy_content.KEYWORDS_ADVENTURES, + ) default_adventures[level].add(adventure) for teacher_adventure in teacher_adventures_db: - adventure = SortedAdventure(short_name=teacher_adventure['id'], - long_name=teacher_adventure['name'], - is_teacher_adventure=True, - is_command_adventure=False) - teacher_adventures[int(teacher_adventure['level'])].add(adventure) + adventure = SortedAdventure( + short_name=teacher_adventure["id"], + long_name=teacher_adventure["name"], + is_teacher_adventure=True, + is_command_adventure=False, + ) + teacher_adventures[int(teacher_adventure["level"])].add(adventure) for level in range(1, hedy.HEDY_MAX_LEVEL + 1): adventures_set = set(adventures[level]) - available_teacher_adventures = teacher_adventures[level].difference(adventures_set) - available_default_adventures = default_adventures[level].difference(adventures_set) - available_adventures[level] = list(available_teacher_adventures.union(available_default_adventures)) + available_teacher_adventures = teacher_adventures[level].difference( + adventures_set + ) + available_default_adventures = default_adventures[level].difference( + adventures_set + ) + available_adventures[level] = list( + available_teacher_adventures.union(available_default_adventures) + ) available_adventures[level].sort(key=lambda item: item.long_name.upper()) return available_adventures @@ -452,15 +592,15 @@ def get_unused_adventures(self, adventures, teacher_adventures_db, adventure_nam def is_adventure_from_teacher(self, adventure_id, teacher_adventures): is_teacher_adventure = False for adventure in teacher_adventures: - if adventure_id == adventure['id']: + if adventure_id == adventure["id"]: is_teacher_adventure = True return is_teacher_adventure @route("/restore-customizations", methods=["POST"]) @requires_teacher def restore_customizations_to_default(self, user): - class_id = session['class_id'] - level = request.args.get('level') + class_id = session["class_id"] + level = request.args.get("level") Class = self.db.get_class(class_id) teacher_adventures = self.db.get_teacher_adventures(user["username"]) @@ -468,24 +608,34 @@ def restore_customizations_to_default(self, user): if not Class or Class["teacher"] != user["username"]: return utils.error_page(error=404, ui_message=gettext("no_such_class")) - customizations, adventures, adventure_names, available_adventures, _ = self.get_class_info( - user, session['class_id']) + ( + customizations, + adventures, + adventure_names, + available_adventures, + _, + ) = self.get_class_info(user, session["class_id"]) db_adventures = {str(i): [] for i in range(1, hedy.HEDY_MAX_LEVEL + 1)} adventures = {i: [] for i in range(1, hedy.HEDY_MAX_LEVEL + 1)} for lvl, default_adventures in hedy_content.ADVENTURE_ORDER_PER_LEVEL.items(): for adventure in default_adventures: - db_adventures[str(lvl)].append({'name': adventure, 'from_teacher': False}) - sorted_adventure = SortedAdventure(short_name=adventure, - long_name=adventure_names[adventure], - is_command_adventure=adventure in hedy_content.KEYWORDS_ADVENTURES, - is_teacher_adventure=False) + db_adventures[str(lvl)].append( + {"name": adventure, "from_teacher": False} + ) + sorted_adventure = SortedAdventure( + short_name=adventure, + long_name=adventure_names[adventure], + is_command_adventure=adventure in hedy_content.KEYWORDS_ADVENTURES, + is_teacher_adventure=False, + ) adventures[lvl].append(sorted_adventure) for adventure in teacher_adventures: - available_adventures[int(adventure['level'])].append( - {"name": adventure['id'], "from_teacher": True}) + available_adventures[int(adventure["level"])].append( + {"name": adventure["id"], "from_teacher": True} + ) customizations = { "id": class_id, @@ -493,76 +643,95 @@ def restore_customizations_to_default(self, user): "opening_dates": {}, "other_settings": [], "level_thresholds": {}, - "sorted_adventures": db_adventures + "sorted_adventures": db_adventures, } self.db.update_class_customizations(customizations) - available_adventures = self.get_unused_adventures(adventures, teacher_adventures, adventure_names) - - return render_partial('customize-class/partial-sortable-adventures.html', - level=level, - customizations=customizations, - adventures=adventures, - max_level=hedy.HEDY_MAX_LEVEL, - adventure_names=adventure_names, - adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, - available_adventures=available_adventures, - class_id=session['class_id']) + available_adventures = self.get_unused_adventures( + adventures, teacher_adventures, adventure_names + ) + + return render_partial( + "customize-class/partial-sortable-adventures.html", + level=level, + customizations=customizations, + adventures=adventures, + max_level=hedy.HEDY_MAX_LEVEL, + adventure_names=adventure_names, + adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, + available_adventures=available_adventures, + class_id=session["class_id"], + ) @route("/restore-adventures/level/", methods=["POST"]) @requires_teacher def restore_adventures_to_default(self, user, level): - class_id = session['class_id'] + class_id = session["class_id"] Class = self.db.get_class(class_id) if not Class or Class["teacher"] != user["username"]: return utils.error_page(error=404, ui_message=gettext("no_such_class")) - customizations, adventures, adventure_names, available_adventures, _ = self.get_class_info( - user, session['class_id']) + ( + customizations, + adventures, + adventure_names, + available_adventures, + _, + ) = self.get_class_info(user, session["class_id"]) teacher_adventures = self.db.get_teacher_adventures(user["username"]) db_adventures = {str(i): [] for i in range(1, hedy.HEDY_MAX_LEVEL + 1)} adventures = {i: [] for i in range(1, hedy.HEDY_MAX_LEVEL + 1)} for lvl, default_adventures in hedy_content.ADVENTURE_ORDER_PER_LEVEL.items(): for adventure in default_adventures: - db_adventures[str(lvl)].append({'name': adventure, 'from_teacher': False}) - sorted_adventure = SortedAdventure(short_name=adventure, - long_name=adventure_names[adventure], - is_command_adventure=adventure in hedy_content.KEYWORDS_ADVENTURES, - is_teacher_adventure=False) + db_adventures[str(lvl)].append( + {"name": adventure, "from_teacher": False} + ) + sorted_adventure = SortedAdventure( + short_name=adventure, + long_name=adventure_names[adventure], + is_command_adventure=adventure in hedy_content.KEYWORDS_ADVENTURES, + is_teacher_adventure=False, + ) adventures[lvl].append(sorted_adventure) - customizations['sorted_adventures'] = db_adventures - available_adventures = self.get_unused_adventures(adventures, teacher_adventures, adventure_names) + customizations["sorted_adventures"] = db_adventures + available_adventures = self.get_unused_adventures( + adventures, teacher_adventures, adventure_names + ) self.db.update_class_customizations(customizations) - return render_partial('customize-class/partial-sortable-adventures.html', - level=level, - customizations=customizations, - adventures=adventures, - max_level=hedy.HEDY_MAX_LEVEL, - adventure_names=adventure_names, - adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, - available_adventures=available_adventures, - class_id=session['class_id']) + return render_partial( + "customize-class/partial-sortable-adventures.html", + level=level, + customizations=customizations, + adventures=adventures, + max_level=hedy.HEDY_MAX_LEVEL, + adventure_names=adventure_names, + adventures_default_order=hedy_content.ADVENTURE_ORDER_PER_LEVEL, + available_adventures=available_adventures, + class_id=session["class_id"], + ) @route("/restore-adventures-modal/level/", methods=["GET"]) @requires_teacher def get_restore_adventures_modal(self, user, level): - Class = self.db.get_class(session['class_id']) + Class = self.db.get_class(session["class_id"]) if not Class or Class["teacher"] != user["username"]: return utils.error_page(error=404, ui_message=gettext("no_such_class")) - modal_text = gettext('reset_adventure_prompt') - htmx_endpoint = f'/for-teachers/restore-adventures/level/{level}' + modal_text = gettext("reset_adventure_prompt") + htmx_endpoint = f"/for-teachers/restore-adventures/level/{level}" htmx_target = "#adventure-dragger" htmx_indicator = "#indicator" - return render_partial('modal/htmx-modal-confirm.html', - modal_text=modal_text, - htmx_endpoint=htmx_endpoint, - htmx_target=htmx_target, - htmx_indicator=htmx_indicator) + return render_partial( + "modal/htmx-modal-confirm.html", + modal_text=modal_text, + htmx_endpoint=htmx_endpoint, + htmx_target=htmx_target, + htmx_indicator=htmx_indicator, + ) @route("/customize-class/", methods=["POST"]) @requires_teacher @@ -615,14 +784,19 @@ def update_customizations(self, user, class_id): "opening_dates": opening_dates, "other_settings": body["other_settings"], "level_thresholds": level_thresholds, - "sorted_adventures": customizations["sorted_adventures"] + "sorted_adventures": customizations["sorted_adventures"], } self.db.update_class_customizations(customizations) - achievement = self.achievements.add_single_achievement(user["username"], "my_class_my_rules") + achievement = self.achievements.add_single_achievement( + user["username"], "my_class_my_rules" + ) if achievement: - return {"achievement": achievement, "success": gettext("class_customize_success")}, 200 + return { + "achievement": achievement, + "success": gettext("class_customize_success"), + }, 200 return {"success": gettext("class_customize_success")}, 200 @route("/create-accounts/", methods=["GET"]) @@ -656,16 +830,24 @@ def store_accounts(self, user): if validation: return validation, 400 if account.get("username").strip().lower() in usernames: - return {"error": gettext("unique_usernames"), "value": account.get("username")}, 200 + return { + "error": gettext("unique_usernames"), + "value": account.get("username"), + }, 200 usernames.append(account.get("username").strip().lower()) # Validation for duplicates in the db classes = self.db.get_teacher_classes(user["username"], False) for account in body.get("accounts", []): - if account.get("class") and account["class"] not in [i.get("name") for i in classes]: + if account.get("class") and account["class"] not in [ + i.get("name") for i in classes + ]: return "not your class", 404 if self.db.user_by_username(account.get("username").strip().lower()): - return {"error": gettext("usernames_exist"), "value": account.get("username").strip().lower()}, 200 + return { + "error": gettext("usernames_exist"), + "value": account.get("username").strip().lower(), + }, 200 # Now -> actually store the users in the db for account in body.get("accounts", []): @@ -674,26 +856,39 @@ def store_accounts(self, user): account["keyword_language"] = g.keyword_lang store_new_student_account(self.db, account, user["username"]) if account.get("class"): - class_id = [i.get("id") for i in classes if i.get("name") == account.get("class")][0] - self.db.add_student_to_class(class_id, account.get("username").strip().lower()) + class_id = [ + i.get("id") + for i in classes + if i.get("name") == account.get("class") + ][0] + self.db.add_student_to_class( + class_id, account.get("username").strip().lower() + ) return {"success": gettext("accounts_created")}, 200 @route("/customize-adventure/view/", methods=["GET"]) @requires_login def view_adventure(self, user, adventure_id): if not is_teacher(user) and not is_admin(user): - return utils.error_page(error=403, ui_message=gettext("retrieve_adventure_error")) + return utils.error_page( + error=403, ui_message=gettext("retrieve_adventure_error") + ) adventure = self.db.get_adventure(adventure_id) if not adventure: return utils.error_page(error=404, ui_message=gettext("no_such_adventure")) if adventure["creator"] != user["username"] and not is_admin(user): - return utils.error_page(error=403, ui_message=gettext("retrieve_adventure_error")) + return utils.error_page( + error=403, ui_message=gettext("retrieve_adventure_error") + ) # Add level to the
 tag to let syntax highlighting know which highlighting we need!
         adventure["content"] = adventure["content"].replace(
-            "
", "
"
+            "
",
+            "
",
+        )
+        adventure["content"] = safe_format(
+            adventure["content"], **hedy_content.KEYWORDS.get(g.keyword_lang)
         )
-        adventure["content"] = safe_format(adventure["content"], **hedy_content.KEYWORDS.get(g.keyword_lang))
 
         return render_template(
             "view-adventure.html",
@@ -717,7 +912,9 @@ def get_adventure_info(self, user, adventure_id):
         for Class in Classes:
             temp = {"name": Class.get("name"), "id": Class.get("id"), "checked": False}
             customizations = self.db.get_class_customizations(Class.get("id"))
-            if customizations and adventure_id in customizations.get("teacher_adventures", []):
+            if customizations and adventure_id in customizations.get(
+                "teacher_adventures", []
+            ):
                 temp["checked"] = True
             class_data.append(temp)
 
@@ -794,7 +991,9 @@ def delete_adventure(self, user, adventure_id):
     def parse_preview_adventure(self):
         body = request.json
         try:
-            code = safe_format(body.get("code"), **hedy_content.KEYWORDS.get(g.keyword_lang))
+            code = safe_format(
+                body.get("code"), **hedy_content.KEYWORDS.get(g.keyword_lang)
+            )
         except BaseException:
             return gettext("something_went_wrong_keyword_parsing"), 400
         return {"code": code}, 200
diff --git a/website/frontend_types.py b/website/frontend_types.py
index 88851ba41f5..62aed57e3c5 100644
--- a/website/frontend_types.py
+++ b/website/frontend_types.py
@@ -48,13 +48,14 @@ def __getitem__(self, key):
     def from_database_row(r):
         """Convert a database row into a typed Program object."""
         return Program(
-            name=r.get('name', ''),
-            code=r.get('code', ''),
-            date=r.get('date', 0),
-            adventure_name=r.get('adventure_name', 'default'),
-            id=r.get('id', ''),
-            public=r.get('public'),
-            submitted=r.get('submitted'))
+            name=r.get("name", ""),
+            code=r.get("code", ""),
+            date=r.get("date", 0),
+            adventure_name=r.get("adventure_name", "default"),
+            id=r.get("id", ""),
+            public=r.get("public"),
+            submitted=r.get("submitted"),
+        )
 
 
 @dataclass
@@ -73,7 +74,9 @@ def from_program(program: Program):
             id=program.id,
             public=program.public,
             submitted=program.submitted,
-            public_url=f'{utils.base_url()}/hedy/{program.id}/view' if program.public or program.submitted else None,
+            public_url=f"{utils.base_url()}/hedy/{program.id}/view"
+            if program.public or program.submitted
+            else None,
         )
 
 
@@ -97,10 +100,11 @@ def __getitem__(self, key):
     @staticmethod
     def from_teacher_adventure_database_row(row):
         return Adventure(
-            short_name=row['id'],
-            name=row['name'],
-            save_name=row['name'],
-            start_code='',  # Teacher adventures don't seem to have this
-            text=row['content'],
+            short_name=row["id"],
+            name=row["name"],
+            save_name=row["name"],
+            start_code="",  # Teacher adventures don't seem to have this
+            text=row["content"],
             is_teacher_adventure=True,
-            is_command_adventure=False)
+            is_command_adventure=False,
+        )
diff --git a/website/log_fetcher.py b/website/log_fetcher.py
index 6fa0a713986..873f084e652 100644
--- a/website/log_fetcher.py
+++ b/website/log_fetcher.py
@@ -25,7 +25,9 @@ def from_env():
             database = config["athena"]["database"]
             s3_output = config["athena"]["s3_output"]
             return AwsAthenaClient(db, database, s3_output)
-        logger.warning("Unable to initialize Athena client (missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY)")
+        logger.warning(
+            "Unable to initialize Athena client (missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY)"
+        )
         return None
 
     def __init__(self, client, database, s3_output):
@@ -33,7 +35,11 @@ def __init__(self, client, database, s3_output):
         self.database = database
         self.s3_output = s3_output
 
-    @retry(stop_max_attempt_number=5, wait_exponential_multiplier=1000, wait_exponential_max=10 * 1000)
+    @retry(
+        stop_max_attempt_number=5,
+        wait_exponential_multiplier=1000,
+        wait_exponential_max=10 * 1000,
+    )
     def poll_query_status(self, query_execution_id):
         result = self.client.get_query_execution(QueryExecutionId=query_execution_id)
         state = result["QueryExecution"]["Status"]["State"]
@@ -69,11 +75,15 @@ def query(self, filters):
         query_exec_id = self.client.start_query(query)
         query_status = self.client.poll_query_status(query_exec_id)
 
-        return QueryResult(query_exec_id, query_status["QueryExecution"]["Status"]["State"])
+        return QueryResult(
+            query_exec_id, query_status["QueryExecution"]["Status"]["State"]
+        )
 
     def get_query_results(self, query_execution_id, next_token=None):
         max_results = config["athena"]["max_results"]
-        data = self.client.get_query_results(query_execution_id, next_token, max_results)
+        data = self.client.get_query_results(
+            query_execution_id, next_token, max_results
+        )
         return self._result_to_response(data, next_token is None), data.get("NextToken")
 
     def _build_query(self, filters):
@@ -111,7 +121,10 @@ def _to_row_response(self, headers, data):
         return (
             {}
             if len(row) != len(headers)
-            else {headers[i]: self._format_if_date(headers[i], row[i]) for i in range(0, len(headers))}
+            else {
+                headers[i]: self._format_if_date(headers[i], row[i])
+                for i in range(0, len(headers))
+            }
         )
 
     def _format_if_date(self, header, value):
diff --git a/website/log_queue.py b/website/log_queue.py
index 71f2e661432..f95b141e437 100644
--- a/website/log_queue.py
+++ b/website/log_queue.py
@@ -38,7 +38,9 @@ def __init__(self, name, batch_window_s, do_print=False):
         self.transmitter = None
         self.do_print = do_print
         self.mutex = threading.Lock()
-        self.thread = threading.Thread(target=self._write_thread, name=f"{name}Writer", daemon=True)
+        self.thread = threading.Thread(
+            target=self._write_thread, name=f"{name}Writer", daemon=True
+        )
         self.thread.start()
 
     def add(self, data):
@@ -136,7 +138,9 @@ def _save_records(self, timestamp, records):
             return self.transmitter(timestamp, records)
         else:
             count = len(records)
-            logger.warning(f"No querylog transmitter configured, {count} records dropped")
+            logger.warning(
+                f"No querylog transmitter configured, {count} records dropped"
+            )
 
     def _write_thread(self):
         """Background thread which will wake up every batch_window_s seconds
diff --git a/website/parsons.py b/website/parsons.py
index 3f31a96fd72..d0d011801e3 100644
--- a/website/parsons.py
+++ b/website/parsons.py
@@ -10,13 +10,24 @@ def __init__(self, parsons):
 
         self.parsons = parsons
 
-    @route("/get-exercise//", methods=["GET"], defaults={'keyword_lang': None})
+    @route(
+        "/get-exercise//",
+        methods=["GET"],
+        defaults={"keyword_lang": None},
+    )
     @route("/get-exercise///", methods=["GET"])
     def get_parsons_exercise(self, level, exercise, keyword_lang):
-        if exercise > self.parsons[g.lang].get_highest_exercise_level(level) or exercise < 1:
+        if (
+            exercise > self.parsons[g.lang].get_highest_exercise_level(level)
+            or exercise < 1
+        ):
             return gettext("exercise_doesnt_exist"), 400
         if keyword_lang:
-            exercise = self.parsons[g.lang].get_parsons_data_for_level_exercise(level, exercise, keyword_lang)
+            exercise = self.parsons[g.lang].get_parsons_data_for_level_exercise(
+                level, exercise, keyword_lang
+            )
         else:
-            exercise = self.parsons[g.lang].get_parsons_data_for_level_exercise(level, exercise, g.keyword_lang)
+            exercise = self.parsons[g.lang].get_parsons_data_for_level_exercise(
+                level, exercise, g.keyword_lang
+            )
         return jsonify(exercise), 200
diff --git a/website/profile.py b/website/profile.py
index cdb9352f63a..47391e8e475 100644
--- a/website/profile.py
+++ b/website/profile.py
@@ -36,7 +36,10 @@ def update_profile(self, user):
         body = request.json
         if not isinstance(body, dict):
             return gettext("ajax_error"), 400
-        if not isinstance(body.get("language"), str) or body.get("language") not in ALL_LANGUAGES.keys():
+        if (
+            not isinstance(body.get("language"), str)
+            or body.get("language") not in ALL_LANGUAGES.keys()
+        ):
             return gettext("language_invalid"), 400
         if (
             not isinstance(body.get("keyword_language"), str)
@@ -58,7 +61,11 @@ def update_profile(self, user):
                 body["birth_year"] = int(body.get("birth_year"))
             except ValueError:
                 return safe_format(gettext("year_invalid"), current_year=str(year)), 400
-            if not isinstance(body.get("birth_year"), int) or body["birth_year"] <= 1900 or body["birth_year"] > year:
+            if (
+                not isinstance(body.get("birth_year"), int)
+                or body["birth_year"] <= 1900
+                or body["birth_year"] > year
+            ):
                 return safe_format(gettext("year_invalid"), current_year=str(year)), 400
         if "gender" in body:
             if body["gender"] not in ["m", "f", "o"]:
@@ -77,7 +84,10 @@ def update_profile(self, user):
                     return gettext("exists_email"), 403
                 token = make_salt()
                 hashed_token = password_hash(token, make_salt())
-                self.db.update_user(user["username"], {"email": email, "verification_pending": hashed_token})
+                self.db.update_user(
+                    user["username"],
+                    {"email": email, "verification_pending": hashed_token},
+                )
                 # If this is an e2e test, we return the email verification token directly instead of emailing it.
                 if is_testing_request(request):
                     resp = {"username": user["username"], "token": hashed_token}
@@ -93,13 +103,17 @@ def update_profile(self, user):
                         # Todo TB: Now we only log to the back-end, would be nice to also return the user some info
                         # We have two options: return an error at this point (don't process changes)
                         # Add a notification to the response, still process the changes
-                        print(f"Profile changes processed for {user['username']}, mail sending invalid")
+                        print(
+                            f"Profile changes processed for {user['username']}, mail sending invalid"
+                        )
 
                 # We check whether the user is in the Mailchimp list.
                 if not is_testing_request(request) and MAILCHIMP_API_URL:
                     # We hash the email with md5 to avoid emails with unescaped characters triggering errors
                     request_path = (
-                        MAILCHIMP_API_URL + "/members/" + hashlib.md5(old_user_email.encode("utf-8")).hexdigest()
+                        MAILCHIMP_API_URL
+                        + "/members/"
+                        + hashlib.md5(old_user_email.encode("utf-8")).hexdigest()
                     )
                     r = requests.get(request_path, headers=MAILCHIMP_API_HEADERS)
                     # If user is subscribed, we remove the old email from the list and add the new one
@@ -111,7 +125,13 @@ def update_profile(self, user):
         username = user["username"]
 
         updates = {}
-        for field in ["country", "birth_year", "gender", "language", "keyword_language"]:
+        for field in [
+            "country",
+            "birth_year",
+            "gender",
+            "language",
+            "keyword_language",
+        ]:
             if field in body:
                 updates[field] = body[field]
             else:
@@ -130,7 +150,10 @@ def update_profile(self, user):
         # We want to check if the user choose a new language, if so -> reload
         # We can use g.lang for this to reduce the db calls
         resp["reload"] = False
-        if session["lang"] != body["language"] or session["keyword_lang"] != body["keyword_language"]:
+        if (
+            session["lang"] != body["language"]
+            or session["keyword_lang"] != body["keyword_language"]
+        ):
             resp["message"] = gettext("profile_updated_reload")
             resp["reload"] = True
         else:
@@ -145,8 +168,19 @@ def get_profile(self, user):
         # The user object we got from 'requires_login' is not fully hydrated yet. Look up the database user.
         user = self.db.user_by_username(user["username"])
 
-        output = {"username": user["username"], "email": user["email"], "language": user.get("language", "en")}
-        for field in ["birth_year", "country", "gender", "prog_experience", "experience_languages", "third_party"]:
+        output = {
+            "username": user["username"],
+            "email": user["email"],
+            "language": user.get("language", "en"),
+        }
+        for field in [
+            "birth_year",
+            "country",
+            "gender",
+            "prog_experience",
+            "experience_languages",
+            "third_party",
+        ]:
             if field in user:
                 output[field] = user[field]
         if "verification_pending" in user:
diff --git a/website/programs.py b/website/programs.py
index 5325efec857..2f65664da1b 100644
--- a/website/programs.py
+++ b/website/programs.py
@@ -7,7 +7,14 @@
 import hedy
 import utils
 from config import config
-from website.auth import current_user, email_base_url, is_admin, requires_admin, requires_login, send_email
+from website.auth import (
+    current_user,
+    email_base_url,
+    is_admin,
+    requires_admin,
+    requires_login,
+    send_email,
+)
 
 from .achievements import Achievements
 from .database import Database
@@ -30,15 +37,17 @@ def __init__(self, db: Database, achievements: Achievements):
         self.db = db
         self.achievements = achievements
 
-    def store_user_program(self,
-                           user,
-                           level: int,
-                           name: str,
-                           code: str,
-                           error: bool,
-                           program_id: Optional[str] = None,
-                           adventure_name: Optional[str] = None,
-                           set_public: Optional[bool] = None):
+    def store_user_program(
+        self,
+        user,
+        level: int,
+        name: str,
+        code: str,
+        error: bool,
+        program_id: Optional[str] = None,
+        adventure_name: Optional[str] = None,
+        set_public: Optional[bool] = None,
+    ):
         """Store a user program (either new or overwrite an existing one).
 
         Returns the program record.
@@ -59,19 +68,19 @@ def store_user_program(self,
         }
 
         if set_public is not None:
-            updates['public'] = 1 if set_public else 0
+            updates["public"] = 1 if set_public else 0
 
         if program_id:
             # FIXME: This should turn into a conditional update
             current_prog = self.db.program_by_id(program_id)
             if not current_prog:
-                raise RuntimeError(f'No program with id: {program_id}')
-            if current_prog['username'] != updates['username']:
-                raise NotYourProgramError('Cannot overwrite other user\'s program')
+                raise RuntimeError(f"No program with id: {program_id}")
+            if current_prog["username"] != updates["username"]:
+                raise NotYourProgramError("Cannot overwrite other user's program")
 
             program = self.db.update_program(program_id, updates)
         else:
-            updates['id'] = uuid.uuid4().hex
+            updates["id"] = uuid.uuid4().hex
             program = self.db.store_program(updates)
             self.db.increase_user_program_count(user["username"])
 
@@ -79,8 +88,12 @@ def store_user_program(self,
         self.achievements.increase_count("saved")
         self.achievements.verify_save_achievements(user["username"], adventure_name)
 
-        querylog.log_value(program_id=program['id'],
-                           adventure_name=adventure_name, error=error, code_lines=len(code.split('\n')))
+        querylog.log_value(
+            program_id=program["id"],
+            adventure_name=adventure_name,
+            error=error,
+            code_lines=len(code.split("\n")),
+        )
 
         return program
 
@@ -109,7 +122,9 @@ def delete_program(self, user):
 
         result = self.db.program_by_id(body["id"])
 
-        if not result or (result["username"] != user["username"] and not is_admin(user)):
+        if not result or (
+            result["username"] != user["username"] and not is_admin(user)
+        ):
             return "", 404
         self.db.delete_program_by_id(body["id"])
         self.db.increase_user_program_count(user["username"], -1)
@@ -123,7 +138,9 @@ def delete_program(self, user):
         ):
             self.db.set_favourite_program(user["username"], None)
 
-        achievement = self.achievements.add_single_achievement(user["username"], "do_you_have_copy")
+        achievement = self.achievements.add_single_achievement(
+            user["username"], "do_you_have_copy"
+        )
         resp = {"message": gettext("delete_success")}
         if achievement:
             resp["achievement"] = achievement
@@ -143,7 +160,9 @@ def check_duplicate_program(self):
         programs = self.db.programs_for_user(current_user()["username"])
         for program in programs:
             if program["name"] == body["name"]:
-                return jsonify({"duplicate": True, "message": gettext("overwrite_warning")})
+                return jsonify(
+                    {"duplicate": True, "message": gettext("overwrite_warning")}
+                )
         return jsonify({"duplicate": False})
 
     @route("/", methods=["POST"])
@@ -158,16 +177,16 @@ def save_program(self, user):
             return "name must be a string", 400
         if not isinstance(body.get("level"), int):
             return "level must be an integer", 400
-        if 'program_id' in body and not isinstance(body.get("program_id"), str):
+        if "program_id" in body and not isinstance(body.get("program_id"), str):
             return "program_id must be a string", 400
-        if 'shared' in body and not isinstance(body.get("shared"), bool):
+        if "shared" in body and not isinstance(body.get("shared"), bool):
             return "shared must be a boolean", 400
         if "adventure_name" in body:
             if not isinstance(body.get("adventure_name"), str):
                 return "if present, adventure_name must be a string", 400
 
         error = None
-        program_id = body.get('program_id')
+        program_id = body.get("program_id")
 
         # We don't NEED to pass this in, but it saves the database a lookup if we do.
         program_public = body.get("shared")
@@ -183,26 +202,31 @@ def save_program(self, user):
             except BaseException:
                 error = True
                 if not body.get("force_save", True):
-                    return jsonify({"parse_error": True, "message": gettext("save_parse_warning")})
+                    return jsonify(
+                        {"parse_error": True, "message": gettext("save_parse_warning")}
+                    )
 
         program = self.logic.store_user_program(
             program_id=program_id,
-            level=body['level'],
-            code=body['code'],
-            name=body['name'],
+            level=body["level"],
+            code=body["code"],
+            name=body["name"],
             user=user,
             error=error,
             set_public=program_public,
-            adventure_name=body.get('adventure_name'))
+            adventure_name=body.get("adventure_name"),
+        )
 
-        return jsonify({
-            "message": gettext("save_success_detail"),
-            "share_message": gettext("copy_clipboard"),
-            "name": program["name"],
-            "id": program['id'],
-            "save_info": SaveInfo.from_program(Program.from_database_row(program)),
-            "achievements": self.achievements.get_earned_achievements(),
-        })
+        return jsonify(
+            {
+                "message": gettext("save_success_detail"),
+                "share_message": gettext("copy_clipboard"),
+                "name": program["name"],
+                "id": program["id"],
+                "save_info": SaveInfo.from_program(Program.from_database_row(program)),
+                "achievements": self.achievements.get_earned_achievements(),
+            }
+        )
 
     @route("/share", methods=["POST"])
     @requires_login
@@ -229,7 +253,9 @@ def share_unshare_program(self, user):
             self.db.set_favourite_program(user["username"], None)
 
         program = self.db.set_program_public_by_id(body["id"], bool(body["public"]))
-        achievement = self.achievements.add_single_achievement(user["username"], "sharing_is_caring")
+        achievement = self.achievements.add_single_achievement(
+            user["username"], "sharing_is_caring"
+        )
 
         resp = {
             "id": body["id"],
@@ -307,8 +333,18 @@ def set_hedy_choice(self, user):
 
         self.db.set_program_as_hedy_choice(body["id"], favourite)
         if favourite:
-            return jsonify({"message": 'Program successfully set as a "Hedy choice" program.'}), 200
-        return jsonify({"message": 'Program successfully removed as a "Hedy choice" program.'}), 200
+            return (
+                jsonify(
+                    {"message": 'Program successfully set as a "Hedy choice" program.'}
+                ),
+                200,
+            )
+        return (
+            jsonify(
+                {"message": 'Program successfully removed as a "Hedy choice" program.'}
+            ),
+            200,
+        )
 
     @route("/report", methods=["POST"])
     @requires_login
diff --git a/website/querylog.py b/website/querylog.py
index af70acaa60f..28259979021 100644
--- a/website/querylog.py
+++ b/website/querylog.py
@@ -22,7 +22,9 @@ def __init__(self, **kwargs):
         self.attributes = kwargs
         self.running_timers = set([])
         loadavg = os.getloadavg()[0] if not IS_WINDOWS else None
-        self.set(start_time=dtfmt(self.start_time), pid=os.getpid(), loadavg=loadavg, fault=0)
+        self.set(
+            start_time=dtfmt(self.start_time), pid=os.getpid(), loadavg=loadavg, fault=0
+        )
 
         dyno = os.getenv("DYNO")
         if dyno:
diff --git a/website/quiz.py b/website/quiz.py
index 943a204523c..bfb876433a8 100644
--- a/website/quiz.py
+++ b/website/quiz.py
@@ -16,7 +16,7 @@
 from .website_module import WebsiteModule, route
 
 MAX_ATTEMPTS = 2
-NO_SUCH_QUESTION = 'No such question'
+NO_SUCH_QUESTION = "No such question"
 
 
 class QuizLogic:
@@ -27,12 +27,16 @@ def __init__(self, db: Database):
 
     def quiz_threshold_for_user(self):
         """Return the minimum quiz percentage the given user has to achieve to pass the quiz."""
-        customizations = self.db.get_student_class_customizations(current_user()['username'])
-        return customizations.get('level_thresholds', {}).get('quiz', 0)
+        customizations = self.db.get_student_class_customizations(
+            current_user()["username"]
+        )
+        return customizations.get("level_thresholds", {}).get("quiz", 0)
 
 
 class QuizModule(WebsiteModule):
-    def __init__(self, db: Database, achievements: Achievements, quizzes: Dict[str, Quizzes]):
+    def __init__(
+        self, db: Database, achievements: Achievements, quizzes: Dict[str, Quizzes]
+    ):
         super().__init__("quiz", __name__, url_prefix="/quiz")
         self.logic = QuizLogic(db)
         self.db = db
@@ -46,8 +50,10 @@ def begin(self, level):
         Initialize a new attempt, then redirect to the form that presents question 1.
         """
         self.initialize_attempt(level)
-        statistics.add(current_user()["username"], lambda id_: self.db.add_quiz_started(id_, level))
-        return redirect(url_for('.current_question'))
+        statistics.add(
+            current_user()["username"], lambda id_: self.db.add_quiz_started(id_, level)
+        )
+        return redirect(url_for(".current_question"))
 
     @route("/current_question", methods=["GET"])
     def current_question(self):
@@ -57,13 +63,15 @@ def current_question(self):
         """
         progress, question = self.current_progress_and_question()
 
-        return render_template('quiz/partial-question.html',
-                               level=progress.level,
-                               question_count=self.question_count(progress.level),
-                               correct_answers_so_far=progress.correct_answers_so_far,
-                               incorrect_answers_so_far=progress.incorrect_answers_so_far,
-                               progress=progress,
-                               question=question)
+        return render_template(
+            "quiz/partial-question.html",
+            level=progress.level,
+            question_count=self.question_count(progress.level),
+            correct_answers_so_far=progress.correct_answers_so_far,
+            incorrect_answers_so_far=progress.incorrect_answers_so_far,
+            progress=progress,
+            question=question,
+        )
 
     @route("/preview-question//", methods=["GET"])
     def preview_question(self, level, question):
@@ -86,12 +94,12 @@ def submit_answer(self):
         progress, question = self.current_progress_and_question()
         question_count = self.question_count(progress.level)
 
-        answer = int(request.form.get('answer', '0'))
+        answer = int(request.form.get("answer", "0"))
         if not answer:
-            return 'No answer given or not an int', 400
+            return "No answer given or not an int", 400
 
         if progress.question_attempt >= MAX_ATTEMPTS:
-            return redirect(url_for('.review_question'))
+            return redirect(url_for(".review_question"))
 
         is_correct = question.correct_answer == answer
 
@@ -119,7 +127,9 @@ def submit_answer(self):
             self.on_quiz_finished(progress)
 
         self.save_progress(progress)
-        return redirect(url_for('.review_question' if question_finished else '.current_question'))
+        return redirect(
+            url_for(".review_question" if question_finished else ".current_question")
+        )
 
     @route("/review_question", methods=["GET"])
     def review_question(self):
@@ -129,20 +139,28 @@ def review_question(self):
         is_correct = progress.last_wrong_answer is None
         question_count = self.question_count(progress.level)
         correct_answer = question.get_choice(question.correct_answer)
-        incorrect_answer = question.get_choice(progress.last_wrong_answer) if progress.last_wrong_answer else None
+        incorrect_answer = (
+            question.get_choice(progress.last_wrong_answer)
+            if progress.last_wrong_answer
+            else None
+        )
 
-        next_question = progress.question + 1 if progress.question + 1 <= question_count else None
+        next_question = (
+            progress.question + 1 if progress.question + 1 <= question_count else None
+        )
 
-        return render_template('quiz/partial-review-question.html',
-                               progress=progress,
-                               question=question,
-                               question_count=question_count,
-                               correct_answers_so_far=progress.correct_answers_so_far,
-                               incorrect_answers_so_far=progress.incorrect_answers_so_far,
-                               correct_answer=correct_answer,
-                               incorrect_answer=incorrect_answer,
-                               next_question=next_question,
-                               is_correct=is_correct)
+        return render_template(
+            "quiz/partial-review-question.html",
+            progress=progress,
+            question=question,
+            question_count=question_count,
+            correct_answers_so_far=progress.correct_answers_so_far,
+            incorrect_answers_so_far=progress.incorrect_answers_so_far,
+            correct_answer=correct_answer,
+            incorrect_answer=incorrect_answer,
+            next_question=next_question,
+            is_correct=is_correct,
+        )
 
     @route("/next_question", methods=["POST"])
     def next_question(self):
@@ -153,29 +171,31 @@ def next_question(self):
         self.save_progress(progress)
 
         if progress.question <= self.question_count(progress.level):
-            return redirect(url_for('.current_question'))
+            return redirect(url_for(".current_question"))
         else:
-            return redirect(url_for('.review_quiz'))
+            return redirect(url_for(".review_quiz"))
 
     @route("/review_quiz", methods=["GET"])
     def review_quiz(self):
         """Review the results of the quiz."""
         progress = self.current_progress()
         if not progress:
-            return redirect(url_for('.begin', level=1))
+            return redirect(url_for(".begin", level=1))
         questions = self.get_all_questions(progress.level)
         total_achievable = sum(q.score for q in questions)
 
         score_percent = round(progress.total_score / total_achievable * 100)
 
         # Certificates are only for logged-in users
-        get_certificate = current_user()['username'] and progress.level == HEDY_MAX_LEVEL
+        get_certificate = (
+            current_user()["username"] and progress.level == HEDY_MAX_LEVEL
+        )
         next_level = progress.level + 1 if progress.level < HEDY_MAX_LEVEL else None
         retake_quiz_level = None
 
         # If you are in a class and the teacher has set quiz completion requirements, we check
         # them here and hide the "next level" button if you haven't met them.
-        if current_user()['username']:
+        if current_user()["username"]:
             threshold = self.logic.quiz_threshold_for_user()
             if score_percent < threshold:
                 # No next level for you
@@ -183,12 +203,14 @@ def review_quiz(self):
                 get_certificate = None
                 retake_quiz_level = progress.level
 
-        return render_template('quiz/partial-review-quiz.html',
-                               next_level=next_level,
-                               progress=progress,
-                               retake_quiz_level=retake_quiz_level,
-                               get_certificate=get_certificate,
-                               score_percent=score_percent)
+        return render_template(
+            "quiz/partial-review-quiz.html",
+            next_level=next_level,
+            progress=progress,
+            retake_quiz_level=retake_quiz_level,
+            get_certificate=get_certificate,
+            score_percent=score_percent,
+        )
 
     def on_quiz_finished(self, progress):
         """Called when the student has answered the last question in the quiz.
@@ -204,44 +226,67 @@ def on_quiz_finished(self, progress):
         achievement = None
         username = current_user()["username"]
         if username:
-            statistics.add(username, lambda id_: self.db.add_quiz_finished(id_, progress.level, progress.total_score))
+            statistics.add(
+                username,
+                lambda id_: self.db.add_quiz_finished(
+                    id_, progress.level, progress.total_score
+                ),
+            )
             # FIXME: I'm pretty sure the types of this code don't work out in case there is more
             # than once achievement at a time, but I don't quite understand well enough how it's
             # supposed to work to fix it.
-            achievement = self.achievements.add_single_achievement(username, "next_question")
+            achievement = self.achievements.add_single_achievement(
+                username, "next_question"
+            )
             if progress.total_score == total_achievable:
                 if achievement:
-                    achievement.append(self.achievements.add_single_achievement(username, "quiz_master")[0])
+                    achievement.append(
+                        self.achievements.add_single_achievement(
+                            username, "quiz_master"
+                        )[0]
+                    )
                 else:
-                    achievement = self.achievements.add_single_achievement(username, "quiz_master")
+                    achievement = self.achievements.add_single_achievement(
+                        username, "quiz_master"
+                    )
             if progress.level == HEDY_MAX_LEVEL:
                 if achievement:
-                    achievement.append(self.achievements.add_single_achievement(username, "hedy_certificate")[0])
+                    achievement.append(
+                        self.achievements.add_single_achievement(
+                            username, "hedy_certificate"
+                        )[0]
+                    )
                 else:
-                    achievement = self.achievements.add_single_achievement(username, "hedy_certificate")
+                    achievement = self.achievements.add_single_achievement(
+                        username, "hedy_certificate"
+                    )
 
         # Add any achievements we accumulated to the session. They will be sent to the
         # client at the next possible opportunity.
         if achievement:
-            session['pending_achievements'] = session.get('pending_achievements', []) + achievement
+            session["pending_achievements"] = (
+                session.get("pending_achievements", []) + achievement
+            )
 
     def initialize_attempt(self, level):
         """Record that we're starting a new attempt."""
-        return self.save_progress(QuizProgress(
-            attempt_id=uuid.uuid4().hex,
-            level=level,
-            question=1,
-            question_attempt=0,
-            total_score=0,
-        ))
+        return self.save_progress(
+            QuizProgress(
+                attempt_id=uuid.uuid4().hex,
+                level=level,
+                question=1,
+                question_attempt=0,
+                total_score=0,
+            )
+        )
 
     def save_progress(self, progress):
-        session['quiz_progress'] = asdict(progress)
+        session["quiz_progress"] = asdict(progress)
         return progress
 
     def current_progress(self):
         """Return the current QuizProgress object."""
-        p = session.get('quiz_progress')
+        p = session.get("quiz_progress")
         try:
             return QuizProgress(**p) if p else None
         except Exception:
@@ -257,24 +302,27 @@ def question_count(self, level):
 
     def get_all_questions(self, level):
         """Return all questions for the given level."""
-        qs = self.my_quiz().get_quiz_data_for_level(level, request.args.get('keyword_lang_override', g.keyword_lang))
+        qs = self.my_quiz().get_quiz_data_for_level(
+            level, request.args.get("keyword_lang_override", g.keyword_lang)
+        )
         return [Question.from_yaml(int(n), q) for n, q in qs.items()]
 
-    def get_question(self, level, question: int) -> 'Question':
+    def get_question(self, level, question: int) -> "Question":
         """Return the indicated question. The question will be returned as ViewModelQuestion object.
 
         The current session's keyword language is used, and an override is possible if
         requested.
         """
         q = self.my_quiz().get_quiz_data_for_level_question(
-            level, question, request.args.get('keyword_lang_override', g.keyword_lang))
+            level, question, request.args.get("keyword_lang_override", g.keyword_lang)
+        )
         return Question.from_yaml(question, q) if q else None
 
     def current_progress_and_question(self):
         """Return the current progress and question objects."""
         progress = self.current_progress()
         if not progress:
-            raise RequestRedirect(url_for('.begin', level=1))
+            raise RequestRedirect(url_for(".begin", level=1))
 
         question = self.get_question(progress.level, progress.question)
         if not question:
@@ -297,6 +345,7 @@ class Question:
 
     A question as it is seen by the templates (not necessarily as it is stored on disk).
     """
+
     number: int
     text: str
     choices: List[Choice]
@@ -315,22 +364,29 @@ def from_yaml(nr, data):
         """Build a ViewModelQuestion from what's written in the YAML."""
         return Question(
             number=nr,
-            text=data.get('question_text', ''),
-            code=data.get('code'),
-            choices=[Choice(number=i + 1, text=opt.get('option', ''), feedback=opt.get('feedback', ''))
-                     for i, opt in enumerate(data.get('mp_choice_options', []))],
+            text=data.get("question_text", ""),
+            code=data.get("code"),
+            choices=[
+                Choice(
+                    number=i + 1,
+                    text=opt.get("option", ""),
+                    feedback=opt.get("feedback", ""),
+                )
+                for i, opt in enumerate(data.get("mp_choice_options", []))
+            ],
             # Correct_answer is 'A', 'B', 'C' in the file, but needs to be 1, 2, 3 because that's
             # what the data has.
-            correct_answer=number_from_letter(data.get('correct_answer', 'A')),
-            hint=data.get('hint', ''),
-            score=int(data.get('question_score', 10)),
-            output=data.get('output'),
+            correct_answer=number_from_letter(data.get("correct_answer", "A")),
+            hint=data.get("hint", ""),
+            score=int(data.get("question_score", 10)),
+            output=data.get("output"),
         )
 
 
 @dataclass
 class QuizProgress:
     """Progress through the quiz."""
+
     level: int
     question: int
     attempt_id: str  # Identifier of the entire quiz
@@ -375,14 +431,18 @@ def correct_answers_so_far(self):
 
     @property
     def incorrect_answers_so_far(self):
-        return set(i + 1 for i in range(self.question) if i + 1 not in self.correctly_answered_questions_numbers)
+        return set(
+            i + 1
+            for i in range(self.question)
+            if i + 1 not in self.correctly_answered_questions_numbers
+        )
 
 
 def number_from_letter(letter):
     """Turn A, B, C into 1, 2, 3."""
-    return ord(letter.upper()) - ord('A') + 1
+    return ord(letter.upper()) - ord("A") + 1
 
 
 def letter_from_number(num):
     """Turn 1, 2, 3 into A, B, C."""
-    return chr(ord('A') + num - 1)
+    return chr(ord("A") + num - 1)
diff --git a/website/s3_logger.py b/website/s3_logger.py
index ab221e052ad..1277d9962a0 100644
--- a/website/s3_logger.py
+++ b/website/s3_logger.py
@@ -13,8 +13,7 @@ def log(self, obj):
 
 
 class S3ParseLogger:
-    """A logger that logs to S3.
-    """
+    """A logger that logs to S3."""
 
     @staticmethod
     def from_env_vars():
diff --git a/website/statistics.py b/website/statistics.py
index 4eb52373a4b..76d7817186b 100644
--- a/website/statistics.py
+++ b/website/statistics.py
@@ -38,7 +38,9 @@ def __init__(self, db: Database):
     @requires_login
     def render_class_stats(self, user, class_id):
         if not is_teacher(user) and not is_admin(user):
-            return utils.error_page(error=403, ui_message=gettext("retrieve_class_error"))
+            return utils.error_page(
+                error=403, ui_message=gettext("retrieve_class_error")
+            )
 
         class_ = self.db.get_class(class_id)
         if not class_ or (class_["teacher"] != user["username"] and not is_admin(user)):
@@ -50,14 +52,16 @@ def render_class_stats(self, user, class_id):
             class_info={"id": class_id, "students": students},
             current_page="my-profile",
             page_title=gettext("title_class statistics"),
-            javascript_page_options=dict(page='class-stats'),
+            javascript_page_options=dict(page="class-stats"),
         )
 
     @route("/logs/class/", methods=["GET"])
     @requires_login
     def render_class_logs(self, user, class_id):
         if not is_teacher(user) and not is_admin(user):
-            return utils.error_page(error=403, ui_message=gettext("retrieve_class_error"))
+            return utils.error_page(
+                error=403, ui_message=gettext("retrieve_class_error")
+            )
 
         class_ = self.db.get_class(class_id)
         if not class_ or (class_["teacher"] != user["username"] and not is_admin(user)):
@@ -79,7 +83,11 @@ def get_class_stats(self, user, class_id):
 
         cls = self.db.get_class(class_id)
         students = cls.get("students", [])
-        if not cls or not students or (cls["teacher"] != user["username"] and not is_admin(user)):
+        if (
+            not cls
+            or not students
+            or (cls["teacher"] != user["username"] and not is_admin(user))
+        ):
             return "No such class or class empty", 403
 
         program_data = self.db.get_program_stats(students, start_date, end_date)
@@ -94,11 +102,20 @@ def get_class_stats(self, user, class_id):
         response = {
             "class": {
                 "per_level": _to_response_per_level(per_level_data),
-                "per_week": _to_response(per_week_data, "week", lambda e: f"L{e['level']}"),
+                "per_week": _to_response(
+                    per_week_data, "week", lambda e: f"L{e['level']}"
+                ),
             },
             "students": {
-                "per_level": _to_response(per_level_per_student, "level", lambda e: e["id"], _to_response_level_name),
-                "per_week": _to_response(per_week_per_student, "week", lambda e: e["id"]),
+                "per_level": _to_response(
+                    per_level_per_student,
+                    "level",
+                    lambda e: e["id"],
+                    _to_response_level_name,
+                ),
+                "per_week": _to_response(
+                    per_week_per_student, "week", lambda e: e["id"]
+                ),
             },
         }
         return jsonify(response)
@@ -107,12 +124,21 @@ def get_class_stats(self, user, class_id):
     @requires_login
     def render_class_grid_overview(self, user, class_id):
         if not is_teacher(user) and not is_admin(user):
-            return utils.error_page(error=403, ui_message=gettext("retrieve_class_error"))
-
-        students, class_, class_adventures_formatted, ticked_adventures, \
-            adventure_names, student_adventures = self.get_grid_info(
-                user, class_id, 1)
-        matrix_values = self.get_matrix_values(students, class_adventures_formatted, ticked_adventures, '1')
+            return utils.error_page(
+                error=403, ui_message=gettext("retrieve_class_error")
+            )
+
+        (
+            students,
+            class_,
+            class_adventures_formatted,
+            ticked_adventures,
+            adventure_names,
+            student_adventures,
+        ) = self.get_grid_info(user, class_id, 1)
+        matrix_values = self.get_matrix_values(
+            students, class_adventures_formatted, ticked_adventures, "1"
+        )
         adventure_names = {value: key for key, value in adventure_names.items()}
 
         if not class_ or (class_["teacher"] != user["username"] and not is_admin(user)):
@@ -134,67 +160,95 @@ def render_class_grid_overview(self, user, class_id):
     @route("/grid_overview/class//level", methods=["GET"])
     @requires_login
     def change_dropdown_level(self, user, class_id):
-        level = request.args.get('level')
-        students, class_, class_adventures_formatted, ticked_adventures, \
-            adventure_names, student_adventures = self.get_grid_info(
-                user, class_id, level)
-        matrix_values = self.get_matrix_values(students, class_adventures_formatted, ticked_adventures, level)
+        level = request.args.get("level")
+        (
+            students,
+            class_,
+            class_adventures_formatted,
+            ticked_adventures,
+            adventure_names,
+            student_adventures,
+        ) = self.get_grid_info(user, class_id, level)
+        matrix_values = self.get_matrix_values(
+            students, class_adventures_formatted, ticked_adventures, level
+        )
         adventure_names = {value: key for key, value in adventure_names.items()}
 
-        return jinja_partials.render_partial("customize-grid/partial-grid-levels.html",
-                                             level=level,
-                                             class_info={"id": class_id, "students": students, "name": class_["name"]},
-                                             current_page="grid_overview",
-                                             max_level=hedy.HEDY_MAX_LEVEL,
-                                             class_adventures=class_adventures_formatted,
-                                             ticked_adventures=ticked_adventures,
-                                             matrix_values=matrix_values,
-                                             adventure_names=adventure_names,
-                                             student_adventures=student_adventures,
-                                             page_title=gettext("title_class grid_overview"),
-                                             )
+        return jinja_partials.render_partial(
+            "customize-grid/partial-grid-levels.html",
+            level=level,
+            class_info={"id": class_id, "students": students, "name": class_["name"]},
+            current_page="grid_overview",
+            max_level=hedy.HEDY_MAX_LEVEL,
+            class_adventures=class_adventures_formatted,
+            ticked_adventures=ticked_adventures,
+            matrix_values=matrix_values,
+            adventure_names=adventure_names,
+            student_adventures=student_adventures,
+            page_title=gettext("title_class grid_overview"),
+        )
 
     @route("/grid_overview/class//level", methods=["POST"])
     @requires_login
     def change_checkbox(self, user, class_id):
-        level = request.args.get('level')
-        student_index = request.args.get('student_index', type=int)
-        adventure_index = request.args.get('adventure_index', type=int)
-        students, class_, class_adventures_formatted, ticked_adventures, adventure_names, _ = self.get_grid_info(
-            user, class_id, level)
-        matrix_values = self.get_matrix_values(students, class_adventures_formatted, ticked_adventures, level)
+        level = request.args.get("level")
+        student_index = request.args.get("student_index", type=int)
+        adventure_index = request.args.get("adventure_index", type=int)
+        (
+            students,
+            class_,
+            class_adventures_formatted,
+            ticked_adventures,
+            adventure_names,
+            _,
+        ) = self.get_grid_info(user, class_id, level)
+        matrix_values = self.get_matrix_values(
+            students, class_adventures_formatted, ticked_adventures, level
+        )
 
         adventure_names = {value: key for key, value in adventure_names.items()}
         current_adventure_name = class_adventures_formatted[level][adventure_index]
         student_adventure_id = f"{students[student_index]}-{adventure_names[current_adventure_name]}-{level}"
 
-        self.db.update_student_adventure(student_adventure_id, matrix_values[student_index][adventure_index])
-        _, _, _, ticked_adventures, _, student_adventures = self.get_grid_info(user, class_id, level)
-        matrix_values[student_index][adventure_index] = not matrix_values[student_index][adventure_index]
-
-        return jinja_partials.render_partial("customize-grid/partial-grid-levels.html",
-                                             level=level,
-                                             class_info={"id": class_id, "students": students, "name": class_["name"]},
-                                             current_page="grid_overview",
-                                             max_level=hedy.HEDY_MAX_LEVEL,
-                                             class_adventures=class_adventures_formatted,
-                                             ticked_adventures=ticked_adventures,
-                                             adventure_names=adventure_names,
-                                             student_adventures=student_adventures,
-                                             matrix_values=matrix_values,
-                                             page_title=gettext("title_class grid_overview"),
-                                             )
-
-    def get_matrix_values(self, students, class_adventures_formatted, ticked_adventures, level):
+        self.db.update_student_adventure(
+            student_adventure_id, matrix_values[student_index][adventure_index]
+        )
+        _, _, _, ticked_adventures, _, student_adventures = self.get_grid_info(
+            user, class_id, level
+        )
+        matrix_values[student_index][adventure_index] = not matrix_values[
+            student_index
+        ][adventure_index]
+
+        return jinja_partials.render_partial(
+            "customize-grid/partial-grid-levels.html",
+            level=level,
+            class_info={"id": class_id, "students": students, "name": class_["name"]},
+            current_page="grid_overview",
+            max_level=hedy.HEDY_MAX_LEVEL,
+            class_adventures=class_adventures_formatted,
+            ticked_adventures=ticked_adventures,
+            adventure_names=adventure_names,
+            student_adventures=student_adventures,
+            matrix_values=matrix_values,
+            page_title=gettext("title_class grid_overview"),
+        )
+
+    def get_matrix_values(
+        self, students, class_adventures_formatted, ticked_adventures, level
+    ):
         rendered_adventures = class_adventures_formatted.get(level)
-        matrix = [[None for _ in range(len(class_adventures_formatted[level]))] for _ in range(len(students))]
+        matrix = [
+            [None for _ in range(len(class_adventures_formatted[level]))]
+            for _ in range(len(students))
+        ]
         for student_index in range(len(students)):
             student_list = ticked_adventures.get(students[student_index])
             if student_list and rendered_adventures:
                 for program in student_list:
-                    if program['level'] == level:
-                        index = rendered_adventures.index(program['name'])
-                        matrix[student_index][index] = 1 if program['ticked'] else 0
+                    if program["level"] == level:
+                        index = rendered_adventures.index(program["name"])
+                        matrix[student_index][index] = 1 if program["ticked"] else 0
         return matrix
 
     @route("/program-stats", methods=["GET"])
@@ -220,44 +274,56 @@ def get_program_stats(self, user):
     def get_grid_info(self, user, class_id, level):
         class_ = self.db.get_class(class_id)
         if hedy_content.Adventures(g.lang).has_adventures():
-            adventures = hedy_content.Adventures(g.lang).get_adventure_keyname_name_levels()
+            adventures = hedy_content.Adventures(
+                g.lang
+            ).get_adventure_keyname_name_levels()
         else:
-            adventures = hedy_content.Adventures("en").get_adventure_keyname_name_levels()
+            adventures = hedy_content.Adventures(
+                "en"
+            ).get_adventure_keyname_name_levels()
 
         students = sorted(class_.get("students", []))
         teacher_adventures = self.db.get_teacher_adventures(user["username"])
 
         class_info = self.db.get_class_customizations(class_id)
-        if class_info and 'adventures' in class_info:
+        if class_info and "adventures" in class_info:
             # it uses the old way so convert it to the new one
-            class_info['sorted_adventures'] = {str(i): [] for i in range(1, hedy.HEDY_MAX_LEVEL + 1)}
-            for adventure, levels in class_info['adventures'].items():
+            class_info["sorted_adventures"] = {
+                str(i): [] for i in range(1, hedy.HEDY_MAX_LEVEL + 1)
+            }
+            for adventure, levels in class_info["adventures"].items():
                 for level in levels:
-                    class_info['sorted_adventures'][str(level)].append(
-                        {"name": adventure, "from_teacher": False})
+                    class_info["sorted_adventures"][str(level)].append(
+                        {"name": adventure, "from_teacher": False}
+                    )
 
             self.db.update_class_customizations(class_info)
         else:
             # Create a new default customizations object in case it doesn't have one
             class_info = self._create_customizations(class_id)
 
-        class_adventures = class_info.get('sorted_adventures')
+        class_adventures = class_info.get("sorted_adventures")
 
         adventure_names = {}
         for adv_key, adv_dic in adventures.items():
             for name, _ in adv_dic.items():
-                adventure_names[adv_key] = hedy_content.get_localized_name(name, g.keyword_lang)
+                adventure_names[adv_key] = hedy_content.get_localized_name(
+                    name, g.keyword_lang
+                )
 
         for adventure in teacher_adventures:
-            adventure_names[adventure['id']] = adventure['name']
+            adventure_names[adventure["id"]] = adventure["name"]
 
         class_adventures_formatted = {}
         for key, value in class_adventures.items():
             adventure_list = []
             for adventure in value:
                 # if the adventure is not in adventure names it means that the data in the customizations is bad
-                if not adventure['name'] == 'next' and adventure['name'] in adventure_names:
-                    adventure_list.append(adventure_names[adventure['name']])
+                if (
+                    not adventure["name"] == "next"
+                    and adventure["name"] in adventure_names
+                ):
+                    adventure_list.append(adventure_names[adventure["name"]])
             class_adventures_formatted[key] = adventure_list
 
         ticked_adventures = {}
@@ -270,37 +336,63 @@ def get_grid_info(self, user, class_id, level):
                 for _, program in programs.items():
                     # Old programs sometimes don't have adventures associated to them
                     # So skip them
-                    if 'adventure_name' not in program:
+                    if "adventure_name" not in program:
                         continue
-                    name = adventure_names.get(program['adventure_name'], program['adventure_name'])
-                    customized_level = class_adventures_formatted.get(str(program['level']))
+                    name = adventure_names.get(
+                        program["adventure_name"], program["adventure_name"]
+                    )
+                    customized_level = class_adventures_formatted.get(
+                        str(program["level"])
+                    )
                     if name in customized_level:
-                        student_adventure_id = f"{student}-{program['adventure_name']}-{level}"
-                        current_adventure = self.db.student_adventure_by_id(student_adventure_id)
+                        student_adventure_id = (
+                            f"{student}-{program['adventure_name']}-{level}"
+                        )
+                        current_adventure = self.db.student_adventure_by_id(
+                            student_adventure_id
+                        )
                         if not current_adventure:
                             # store the adventure in case it's not in the table
                             current_adventure = self.db.store_student_adventure(
-                                dict(id=f"{student_adventure_id}", ticked=False, program_id=program['id']))
-
-                        current_program = dict(id=program['id'], level=str(program['level']),
-                                               name=name, ticked=current_adventure['ticked'])
-
-                        student_adventures[student_adventure_id] = program['id']
+                                dict(
+                                    id=f"{student_adventure_id}",
+                                    ticked=False,
+                                    program_id=program["id"],
+                                )
+                            )
+
+                        current_program = dict(
+                            id=program["id"],
+                            level=str(program["level"]),
+                            name=name,
+                            ticked=current_adventure["ticked"],
+                        )
+
+                        student_adventures[student_adventure_id] = program["id"]
                         ticked_adventures[student].append(current_program)
 
-        return students, class_, class_adventures_formatted, ticked_adventures, adventure_names, student_adventures
+        return (
+            students,
+            class_,
+            class_adventures_formatted,
+            ticked_adventures,
+            adventure_names,
+            student_adventures,
+        )
 
     def _create_customizations(self, class_id):
         sorted_adventures = {}
         for lvl, adventures in hedy_content.ADVENTURE_ORDER_PER_LEVEL.items():
-            sorted_adventures[str(lvl)] = [{'name': adventure, 'from_teacher': False} for adventure in adventures]
+            sorted_adventures[str(lvl)] = [
+                {"name": adventure, "from_teacher": False} for adventure in adventures
+            ]
         customizations = {
             "id": class_id,
             "levels": [i for i in range(1, hedy.HEDY_MAX_LEVEL + 1)],
             "opening_dates": {},
             "other_settings": [],
             "level_thresholds": {},
-            "sorted_adventures": sorted_adventures
+            "sorted_adventures": sorted_adventures,
         }
         self.db.update_class_customizations(customizations)
         return customizations
@@ -326,24 +418,43 @@ def add(username, action):
 
 def _to_response_per_level(data):
     data.sort(key=lambda el: el["level"])
-    return [{"level": f"L{entry['level']}", "data": _data_to_response_per_level(entry["data"])} for entry in data]
+    return [
+        {
+            "level": f"L{entry['level']}",
+            "data": _data_to_response_per_level(entry["data"]),
+        }
+        for entry in data
+    ]
 
 
 def _data_to_response_per_level(data):
     res = {}
 
-    _add_value_to_result(res, "successful_runs", data["successful_runs"], is_counter=True)
+    _add_value_to_result(
+        res, "successful_runs", data["successful_runs"], is_counter=True
+    )
     _add_value_to_result(res, "failed_runs", data["failed_runs"], is_counter=True)
-    res["error_rate"] = _calc_error_rate(data.get("failed_runs"), data.get("successful_runs"))
+    res["error_rate"] = _calc_error_rate(
+        data.get("failed_runs"), data.get("successful_runs")
+    )
     _add_exception_data(res, data)
 
     _add_value_to_result(res, "anonymous_runs", data["anonymous_runs"], is_counter=True)
     _add_value_to_result(res, "logged_runs", data["logged_runs"], is_counter=True)
     _add_value_to_result(res, "student_runs", data["student_runs"], is_counter=True)
-    _add_value_to_result(res, "user_type_unknown_runs", data["user_type_unknown_runs"], is_counter=True)
-
-    _add_value_to_result(res, "abandoned_quizzes", data["total_attempts"] - data["completed_attempts"], is_counter=True)
-    _add_value_to_result(res, "completed_quizzes", data["completed_attempts"], is_counter=True)
+    _add_value_to_result(
+        res, "user_type_unknown_runs", data["user_type_unknown_runs"], is_counter=True
+    )
+
+    _add_value_to_result(
+        res,
+        "abandoned_quizzes",
+        data["total_attempts"] - data["completed_attempts"],
+        is_counter=True,
+    )
+    _add_value_to_result(
+        res, "completed_quizzes", data["completed_attempts"], is_counter=True
+    )
 
     min_, max_, avg_ = _score_metrics(data["scores"])
     _add_value_to_result(res, "quiz_score_min", min_)
@@ -368,17 +479,46 @@ def _to_response(data, values_field, series_selector, values_map=None):
             res[values] = {}
 
         d = e["data"]
-        _add_dict_to_result(res[values], "successful_runs", series, d["successful_runs"], is_counter=True)
-        _add_dict_to_result(res[values], "failed_runs", series, d["failed_runs"], is_counter=True)
         _add_dict_to_result(
-            res[values], "abandoned_quizzes", series, d["total_attempts"] - d["completed_attempts"], is_counter=True
+            res[values],
+            "successful_runs",
+            series,
+            d["successful_runs"],
+            is_counter=True,
+        )
+        _add_dict_to_result(
+            res[values], "failed_runs", series, d["failed_runs"], is_counter=True
+        )
+        _add_dict_to_result(
+            res[values],
+            "abandoned_quizzes",
+            series,
+            d["total_attempts"] - d["completed_attempts"],
+            is_counter=True,
+        )
+        _add_dict_to_result(
+            res[values],
+            "completed_quizzes",
+            series,
+            d["completed_attempts"],
+            is_counter=True,
         )
-        _add_dict_to_result(res[values], "completed_quizzes", series, d["completed_attempts"], is_counter=True)
 
-        _add_value_to_result(res[values], "anonymous_runs", d["anonymous_runs"], is_counter=True)
-        _add_value_to_result(res[values], "logged_runs", d["logged_runs"], is_counter=True)
-        _add_value_to_result(res[values], "student_runs", d["student_runs"], is_counter=True)
-        _add_value_to_result(res[values], "user_type_unknown_runs", d["user_type_unknown_runs"], is_counter=True)
+        _add_value_to_result(
+            res[values], "anonymous_runs", d["anonymous_runs"], is_counter=True
+        )
+        _add_value_to_result(
+            res[values], "logged_runs", d["logged_runs"], is_counter=True
+        )
+        _add_value_to_result(
+            res[values], "student_runs", d["student_runs"], is_counter=True
+        )
+        _add_value_to_result(
+            res[values],
+            "user_type_unknown_runs",
+            d["user_type_unknown_runs"],
+            is_counter=True,
+        )
 
         min_, max_, avg_ = _score_metrics(d["scores"])
         _add_dict_to_result(res[values], "quiz_score_min", series, min_)
@@ -387,7 +527,9 @@ def _to_response(data, values_field, series_selector, values_map=None):
 
         _add_exception_data(res[values], d)
 
-    result = [{values_field: k, "data": _add_error_rate_from_dicts(v)} for k, v in res.items()]
+    result = [
+        {values_field: k, "data": _add_error_rate_from_dicts(v)} for k, v in res.items()
+    ]
     result.sort(key=lambda el: el[values_field])
 
     return [values_map(e) for e in result] if values_map else result
@@ -514,7 +656,9 @@ def _add_error_rate_from_dicts(data):
     failed = data.get("failed_runs") or {}
     successful = data.get("successful_runs") or {}
     keys = set.union(set(failed.keys()), set(successful.keys()))
-    data["error_rate"] = {k: _calc_error_rate(failed.get(k), successful.get(k)) for k in keys}
+    data["error_rate"] = {
+        k: _calc_error_rate(failed.get(k), successful.get(k)) for k in keys
+    }
     return data
 
 
@@ -535,7 +679,9 @@ def get_general_class_stats(students):
 
     for entry in data:
         entry_successes = int(entry.get("successful_runs", 0))
-        entry_errors = sum([v for k, v in entry.items() if k.lower().endswith("exception")])
+        entry_errors = sum(
+            [v for k, v in entry.items() if k.lower().endswith("exception")]
+        )
         successes += entry_successes
         errors += entry_errors
         if entry.get("week") == current_week:
diff --git a/website/yaml_file.py b/website/yaml_file.py
index 018a7652027..99d5392b7e6 100644
--- a/website/yaml_file.py
+++ b/website/yaml_file.py
@@ -68,8 +68,11 @@ def __init__(self, filename):
         because it creates a mess of files.
         """
         self.filename = filename
-        self.pickle_filename = path.join(tempfile.gettempdir(), 'hedy_pickles',
-                                         f"{pathname_slug(self.filename)}.pickle")
+        self.pickle_filename = path.join(
+            tempfile.gettempdir(),
+            "hedy_pickles",
+            f"{pathname_slug(self.filename)}.pickle",
+        )
 
     def exists(self):
         return os.path.exists(self.filename)
@@ -92,7 +95,7 @@ def access(self):
         """
         # Obtain or create a per-request cache dictionary (if we have a request), or an unattached
         # cache object that will disappear after this function returns if we don't have a request.
-        yaml_cache = g.setdefault('yaml_cache', {}) if has_request_context() else {}
+        yaml_cache = g.setdefault("yaml_cache", {}) if has_request_context() else {}
         cached = yaml_cache.get(self.filename)
         if cached is not None:
             return cached
@@ -100,7 +103,9 @@ def access(self):
         data = self.load()
 
         if not isinstance(data, dict):
-            raise RuntimeError(f"Contents of {self.filename} needs to be a dict, got: {data}")
+            raise RuntimeError(
+                f"Contents of {self.filename} needs to be a dict, got: {data}"
+            )
 
         yaml_cache[self.filename] = data
         return data
@@ -126,16 +131,16 @@ def load(self):
             with atomic_write_file(self.pickle_filename) as f:
                 pickle.dump(data, f)
         except IOError as e:
-            logger.warn('Error writing pickled YAML: %s', e)
+            logger.warn("Error writing pickled YAML: %s", e)
 
         return data
 
-    @querylog.timed_as('load_yaml_pickled')
+    @querylog.timed_as("load_yaml_pickled")
     def load_pickle(self):
         with open(self.pickle_filename, "rb") as f:
             return pickle.load(f)
 
-    @querylog.timed_as('load_yaml_uncached')
+    @querylog.timed_as("load_yaml_uncached")
     def load_uncached(self):
         """Load the source YAML file."""
         try:
@@ -186,7 +191,7 @@ def pathname_slug(x):
     that the full path may be too long to use as a file name, and that
     it needs to remain unique.
     """
-    x = re.sub(r'[^a-zA-Z0-9_-]', '', x)
+    x = re.sub(r"[^a-zA-Z0-9_-]", "", x)
     return x[-20:] + md5digest(x)