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()
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 )
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('')
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
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()
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()
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)
def error(self, message): raise Failure()
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()