Ejemplo n.º 1
0
def play(challenge, workspace, keys=None, diff=False):
    logger.info('play(...)')

    infile = os.path.join(workspace, 'in')
    if challenge.in_extension:
        infile += challenge.in_extension
    outfile = os.path.join(workspace, 'out')
    if challenge.out_extension:
        outfile += challenge.out_extension
    scriptfile = os.path.join(workspace, 'script')
    logfile = os.path.join(workspace, 'log')
    with open(outfile, 'w') as f:
        f.write(challenge.out_text)

    write('Launching vimgolf session', fg='yellow')
    main_loop(
        challenge=challenge,
        infile=infile,
        logfile=logfile,
        outfile=outfile,
        scriptfile=scriptfile,
        initial_keys=keys,
        diff=diff,
    )
    write('Thanks for playing!', fg='green')
Ejemplo n.º 2
0
def fetch_and_validate_challenge(challenge_id):
    write('Downloading vimgolf challenge {}'.format(challenge_id), fg='yellow')
    challenge = Challenge(challenge_id)
    challenge.load_or_download()
    challenge_spec = challenge.spec
    compliant = challenge_spec.get('client') == RUBY_CLIENT_VERSION_COMPLIANCE
    if not compliant:
        message = 'vimgolf=={} is not compliant with vimgolf.com'.format(
            __version__)
        write(message, err=True, fg='red')
        write('Uploading to vimgolf.com is disabled', err=True, fg='red')
        write('vimgolf may not function properly', fg='red')
        try:
            from distutils.version import StrictVersion
            client_compliance_version = StrictVersion(
                RUBY_CLIENT_VERSION_COMPLIANCE)
            api_version = StrictVersion(challenge_spec['client'])
            action = 'upgrade' if api_version > client_compliance_version else 'downgrade'
        except Exception:
            action = 'update'
        write('Please {} vimgolf to a compliant version'.format(action),
              fg='yellow')
        if not confirm('Try to play without uploads?'):
            raise Failure()
    return compliant
Ejemplo n.º 3
0
def vim(args, **run_kwargs):
    try:
        _vim(args, **run_kwargs)
    except Failure:
        raise
    except Exception:
        logger.exception('{} execution failed'.format(GOLF_VIM))
        write('The execution of {} has failed'.format(GOLF_VIM),
              err=True,
              fg='red')
        raise Failure()
Ejemplo n.º 4
0
def inspect(challenge_id, keys, literal_lt, literal_gt):
    challenge_id = expand_challenge_id(challenge_id)
    logger.info('inspect(%s)', challenge_id)

    if not validate_challenge_id(challenge_id):
        show_challenge_id_error()
        raise Failure()

    try:
        challenge = Challenge(challenge_id)
        challenge.load_or_download()
    except Failure:
        raise
    except Exception:
        logger.exception('challenge retrieval failed')
        write('The challenge retrieval has failed', err=True, fg='red')
        write('Please check the challenge ID on vimgolf.com', err=True, fg='red')
        raise Failure()

    src_in_path = challenge.in_path
    name, ext = os.path.splitext(os.path.basename(src_in_path))

    sequences = build_sequences(
        keys=keys,
        literal_gt=literal_gt,
        literal_lt=literal_lt
    )

    with tempfile.TemporaryDirectory() as workspace:
        zfill = lambda s: str(s).zfill(3)

        def dst_path(index):
            return os.path.join(workspace, '{}{}{}'.format(name, zfill(index), ext))

        def script_path(index):
            return os.path.join(workspace, 'mapping{}.vim'.format(zfill(index)))

        def in_path(index):
            return os.path.join(workspace, 'inspect-{}{}{}'.format(name, zfill(index), ext))

        replay_sequences(
            dst_path=dst_path,
            script_path=script_path,
            sequences=sequences,
            src_in_path=src_in_path
        )
        inspect_sequences(
            workspace=workspace,
            dst_path=dst_path,
            in_path=in_path,
            sequences=sequences
        )
Ejemplo n.º 5
0
def config(api_key=None):
    logger.info('config(...)')
    if api_key is not None and not validate_api_key(api_key):
        show_api_key_error()
        raise Failure()
    if api_key:
        set_api_key(api_key)
    else:
        api_key = get_api_key()
        if api_key:
            write(api_key)
        else:
            show_api_key_help()
Ejemplo n.º 6
0
def put(challenge_id, keys, diff=False):
    challenge_id = expand_challenge_id(challenge_id)
    logger.info('put(%s)', challenge_id)

    if not validate_challenge_id(challenge_id):
        show_challenge_id_error()
        raise Failure()

    api_key = get_api_key()
    if not validate_api_key(api_key):
        write('An API key has not been configured', fg='red')
        write('Uploading to vimgolf.com is disabled', fg='red')
        show_api_key_help()
        if not confirm('Play without uploads?'):
            raise Failure()

    try:
        compliant = fetch_and_validate_challenge(challenge_id)
    except Failure:
        raise
    except Exception:
        logger.exception('challenge retrieval failed')
        write('The challenge retrieval has failed', err=True, fg='red')
        write('Please check the challenge ID on vimgolf.com',
              err=True,
              fg='red')
        raise Failure()

    challenge = Challenge(id=challenge_id,
                          compliant=compliant,
                          api_key=api_key).load()
    with tempfile.TemporaryDirectory() as d:
        play(challenge, d, keys=keys, diff=diff)
    challenge.update_metadata()
Ejemplo n.º 7
0
def ls(incomplete=False, page=None, limit=LISTING_LIMIT):
    logger.info('list_(%s, %s)', page, limit)
    stored_challenges = get_stored_challenges()
    try:
        url = GOLF_HOST
        if page is not None:
            url = urllib.parse.urljoin(GOLF_HOST, '/?page={}'.format(page))
        response = http_request(url)
        listings = extract_listings_from_page(
            page_html=response.body,
            limit=limit,
            stored_challenges=stored_challenges)
    except Failure:
        raise
    except Exception:
        logger.exception('challenge retrieval failed')
        write('The challenge list retrieval has failed', err=True, fg='red')
        raise Failure()

    table_rows = [['#', 'Name', 'Entries', 'ID', '⬆', 'Score', 'Answers']]
    for idx, listing in enumerate(listings):
        if incomplete and listing.uploaded:
            continue
        table_row = [
            '{}{} '.format(EXPANSION_PREFIX, idx + 1),
            listing.name,
            listing.n_entries,
            style(listing.id, fg='yellow'),
            bool_to_mark(listing.uploaded),
            listing.score if listing.score and listing.score > 0 else '-',
            listing.answers or '-',
        ]
        table_rows.append(table_row)

    write(AsciiTable(table_rows).table)

    id_lookup = {
        str(idx + 1): listing.id
        for idx, listing in enumerate(listings)
    }
    set_id_lookup(id_lookup)
Ejemplo n.º 8
0
def _vim(args, **run_kwargs):
    vim_path = find_executable(GOLF_VIM)
    if not vim_path:
        write('Unable to find "{}"'.format(GOLF_VIM), fg='red')
        write('Please update your PATH to include the directory with "{}"'.
              format(GOLF_VIM),
              fg='red')
        raise Failure()
    vim_name = os.path.basename(os.path.realpath(vim_path))

    if sys.platform == 'win32':
        # Remove executable extension (.exe, .bat, .cmd, etc.) from 'vim_name'
        base, ext = os.path.splitext(vim_name)
        pathexts = os.environ.get('PATHEXT', '.EXE').split(os.pathsep)
        for pathext in pathexts:
            if ext.upper() == pathext.upper():
                vim_name = base
                break

    # As of 2019/3/2, on Windows, nvim-qt doesn't support --nofork.
    # Issue a warning as opposed to failing, since this may change.
    if vim_name == 'nvim-qt' and sys.platform == 'win32':
        write('vimgolf with nvim-qt on Windows may not function properly',
              fg='red')
        write(
            'If there are issues, please try using a different version of vim',
            fg='yellow')
        if not confirm('Continue trying to play?'):
            raise Failure()

    # Configure args used by all vim invocations (for both playing and diffing)
    # 'vim_path' is used instead of GOLF_VIM to handle 'vim.bat' on the PATH.
    # subprocess.run would not launch vim.bat with GOLF_VIM == 'vim', but 'find_executable'
    # will return the full path to vim.bat in that case.
    vim_args = [vim_path]
    # Add --nofork so gvim, mvim, and nvim-qt don't return immediately
    # Add special-case handling since nvim doesn't accept that option.
    if vim_name != 'nvim':
        vim_args.append('--nofork')
    # For nvim-qt, options after '--' are passed to nvim.
    if vim_name == 'nvim-qt':
        vim_args.append('--')
    vim_args.extend(args)
    subprocess.run(vim_args, **run_kwargs)
    # On Windows, vimgolf freezes when reading input after nvim's exit.
    # For an unknown reason, shell'ing out an effective no-op works-around the issue
    if vim_name == 'nvim' and sys.platform == 'win32':
        os.system('')
Ejemplo n.º 9
0
def show_api_key_error():
    write('Invalid API key', err=True, fg='red')
    write('Please check your API key on vimgolf.com', err=True, fg='red')
Ejemplo n.º 10
0
def show_api_key_help():
    write('An API key can be obtained from vimgolf.com', fg='yellow')
    write('Please run "vimgolf config API_KEY" to set your API key', fg='yellow')
Ejemplo n.º 11
0
def main_loop(challenge, infile, logfile, outfile, scriptfile, initial_keys, diff):
    keys = initial_keys
    while True:
        with open(infile, 'w') as f:
            f.write(challenge.in_text)
        with open(scriptfile, 'w') as f:
            if keys:
                f.write(KeycodeReprs(keys).call_feedkeys)

        if not diff:
            play_result = play_single(
                infile=infile,
                logfile=logfile,
                outfile=outfile,
                scriptfile=scriptfile,
            )

            raw_keys = play_result['raw_keys']
            keycode_reprs = play_result['keycode_reprs']
            correct = play_result['correct']
            score = play_result['score']

            write('Here are your keystrokes:', fg='green')
            for keycode_repr in keycode_reprs:
                color = 'magenta' if len(keycode_repr) > 1 else None
                write(keycode_repr, fg=color, nl=False)
            write()

            if correct:
                write('Success! Your output matches.', fg='green')
                write('Your score:', fg='green')
            else:
                write('Uh oh, looks like your entry does not match the desired output.', fg='red')
                write('Your score for this failed attempt:', fg='red')
            write(score)
        else:
            correct = False
            raw_keys = ''
            keycode_reprs = ''
            score = 0

        menu_loop_result = menu_loop(
            challenge=challenge,
            correct=correct,
            infile=infile,
            outfile=outfile,
            raw_keys=raw_keys,
            diff=diff,
        )
        keys = menu_loop_result['keys']

        if challenge.id and not diff:
            challenge.add_answer(
                keys=keycode_reprs,
                score=score,
                correct=correct,
                uploaded=menu_loop_result['uploaded'],
            )

        if menu_loop_result['should_quit']:
            break
Ejemplo n.º 12
0
def menu_loop(
        challenge,
        correct,
        infile,
        outfile,
        raw_keys,
        diff):
    upload_eligible = (
        challenge.id and
        challenge.compliant and
        challenge.api_key and
        not diff
    )
    uploaded = False
    while True:
        # Generate the menu items inside the loop since it can change across iterations
        # (e.g., upload option can be removed)
        menu = []
        if not correct:
            menu.append(('d', 'Show diff'))
        if upload_eligible and correct:
            menu.append(('w', 'Upload result'))
        menu.append(('r', 'Retry the current challenge'))
        menu.append(('k', 'Retry the current challenge with key sequence'))
        menu.append(('q', 'Quit vimgolf'))
        valid_codes = [x[0] for x in menu]
        if diff:
            selection = 'd'
        else:
            for opt in menu:
                write('[{}] {}'.format(*opt), fg='yellow')
            selection = input_loop('Choice> ')
        if selection not in valid_codes:
            write('Invalid selection: {}'.format(selection), err=True, fg='red')
        elif selection == 'd':
            diff_args = ['-d', '-n', infile, outfile]
            vim(diff_args)
            if diff:
                selection = 'q'
                break
        elif selection == 'w':
            success = upload_result(challenge.id, challenge.api_key, raw_keys)
            if success:
                write('Uploaded entry!', fg='green')
                leaderboard_url = get_challenge_url(challenge.id)
                write('View the leaderboard: {}'.format(leaderboard_url), fg='green')
                uploaded = True
                upload_eligible = False
            else:
                write('The entry upload has failed', err=True, fg='red')
                message = 'Please check your API key on vimgolf.com'
                write(message, err=True, fg='red')
        else:
            break
    should_quit = selection == 'q'
    keys = None
    if not should_quit:
        if selection == 'k':
            keys = input_loop('Enter key sequence: ')
        write('Retrying vimgolf challenge', fg='yellow')
    return {
        'should_quit': should_quit,
        'uploaded': uploaded,
        'keys': keys,
    }
Ejemplo n.º 13
0
def show_challenge_id_error():
    write('Invalid challenge ID', err=True, fg='red')
    write('Please check the ID on vimgolf.com', err=True, fg='red')
Ejemplo n.º 14
0
def show(challenge_id):
    challenge_id = expand_challenge_id(challenge_id)
    logger.info('show(%s)', challenge_id)

    if not validate_challenge_id(challenge_id):
        show_challenge_id_error()
        raise Failure()

    try:
        fetched = fetch_challenge_spec_and_page(challenge_id)
        challenge = fetched['challenge']
        page_response = fetched['page']
        page_url = fetched['url']

        data = extract_data_from_page(page_response.body)
        name = data['name']
        description = data['description']
        leaders = data['leaders']

        challenge.update_metadata(name, description)
        metadata = challenge.metadata

        start_file = challenge.in_text
        if not start_file.endswith('\n'):
            start_file += '\n'
        end_file = challenge.out_text
        if not end_file.endswith('\n'):
            end_file += '\n'

        separator = '-' * 50
        write(separator)
        write('{} ('.format(name), nl=False)
        write(challenge_id, fg='yellow', nl=False)
        write(')')
        write(separator)
        write(page_url)
        write(separator)
        write('Leaderboard', fg='green')
        if leaders:
            for leader in leaders[:LEADER_LIMIT]:
                write('{} {}'.format(leader.username.ljust(15), leader.score))
            if len(leaders) > LEADER_LIMIT:
                write('...')
        else:
            write('no entries yet', fg='yellow')
        write(separator)
        write(description)
        write(separator)
        write('Start File', fg='green')
        write(start_file, nl=False)
        write(separator)
        write('End File', fg='green')
        write(end_file, nl=False)
        write(separator)
        write('Stats', fg='green')
        write('Entered Solutions: {}'.format(metadata['answers']))
        write('Uploaded: {}'.format(metadata['uploaded']))
        write('Correct Solutions: {}'.format(metadata['correct']))
        write('Self Best Score: {}'.format(metadata['best_score']))
        answers = challenge.answers
        ignored_answer_suffix = 'ZQ'
        answer_rows = [['Keys', 'Correct', 'Submitted', 'Score', 'Timestamp']]
        for answer in answers:
            keys = ''.join(answer['keys'])
            if keys.endswith(ignored_answer_suffix):
                continue
            answer_row = [
                keys,
                bool_to_mark(answer['correct']),
                bool_to_mark(answer['uploaded']),
                answer['score'],
                answer['timestamp'],
            ]
            answer_rows.append(answer_row)
        if len(answer_rows) > 1:
            write(AsciiTable(answer_rows).table)
    except Failure:
        raise
    except Exception:
        logger.exception('challenge retrieval failed')
        write('The challenge retrieval has failed', err=True, fg='red')
        write('Please check the challenge ID on vimgolf.com', err=True, fg='red')
        raise Failure()
Ejemplo n.º 15
0
def version():
    """display the version number"""
    write(__version__)