Skip to content

Commit

Permalink
Merge pull request #517 from France-ioi/localwork-link-option
Browse files Browse the repository at this point in the history
Command-line dialog improvements
  • Loading branch information
SebastienTainon authored Oct 2, 2024
2 parents ab4aac9 + b5d96d3 commit a049ea0
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 3 deletions.
196 changes: 196 additions & 0 deletions assets/fioi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#!/usr/bin/env python3
import argparse, json, os, requests, time

ENDPOINT_URL = 'https://codecast-backend.france-ioi.org'
HISTORY_FILE = os.path.expanduser('~/.fioi_history.json')


def history_load(idx=None):
if idx is None:
try:
with open(HISTORY_FILE, 'r') as file:
history = json.load(file)

return history
except:
return []

try:
with open(HISTORY_FILE, 'r') as file:
history = json.load(file)

return history[idx]
except:
return None

def history_save(data):
try:
with open(HISTORY_FILE, 'r') as file:
history = json.load(file)
except:
history = []

history.insert(0, data)
history = history[-10:]

try:
with open(HISTORY_FILE, 'w') as file:
json.dump(history, file)
except:
pass


def make_payload_from_args(args):
with open(args.file_path, 'r') as file:
source_code = file.read()

payload = {
"token": args.token,
"platform": args.platform,
"answer": {
"sourceCode": source_code,
"fileName": os.path.basename(args.file_path),
"language": args.language
},
"sLocale": "fr"
}

return payload


def send_submission(payload):
url = f'{ENDPOINT_URL}/submissions-offline'
headers = {
"Accept": "application/json",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)

if response.status_code == 200:
json_response = response.json()
if json_response.get('success'):
submission_id = json_response.get('submissionId')
print('Submission sent successfully!')
return submission_id
else:
print('Failed to send submission.')
elif response.status_code == 500:
print('Token is invalid.')
else:
print(f'Failed to send submission. Status code: {response.status_code}')


def check_submission_status(submission_id):
url = f'{ENDPOINT_URL}/submissions/{submission_id}'
print("Waiting for evaluation...", end='')
while True:
response = requests.get(url)
if response.status_code == 200:
json_response = response.json()
if json_response.get('evaluated'):
print(' evaluation successful!')
return json_response
else:
print('.', end='')
time.sleep(1)
elif response.status_code == 404:
print('Submission not found.')
break
else:
print(f'Failed to check submission status. Status code: {response.status_code}')
time.sleep(1)


def display_submission_info(data, verbose):
print(f"Score: {data['score']}")

if data["compilationError"]:
print("Your submission did not compile:")
print(data["compilationMessage"])
return

print(f"Passed {data['passedTestsCount']} out of {data['totalTestsCount']} tests.")
if len(data["subTasks"]) > 0:
print(f"Scores per subtask: {'+'.join(map(lambda x: str(x['score']), data['subTasks']))}")
print()

if not verbose:
print("Use ./fioi.py display 0 -v to display individual test information.")
return

print()
for idx, test in enumerate(data["tests"]):
print(f"Test #{idx}")
print("Score:", test["score"])
if not test["noFeedback"]:
if test["timeMs"] != -1:
print(f"Time taken: {test['timeMs']}ms")
if test["memoryKb"] != 0:
print(f"Memory used: (KB): {test['memoryKb']}KB")
if test["errorMessage"] != '':
print("Error Code:", test["errorCode"])
print("Error Message:", test["errorMessage"])
if test["log"].strip() != '':
print("Evaluation message:")
print(test["log"].strip())
else:
print("Information for this test is hidden.")
print()


def process_submit(args):
payload = make_payload_from_args(args)
submission_id = send_submission(payload)

if submission_id is not None:
result = check_submission_status(submission_id)
if result is not None:
print()
display_submission_info(result, args.verbose)
result["timestamp"] = time.time()
history_save(result)


def process_display(args):
data = history_load(args.idx)
if args.idx is None:
if data is None:
print("No evaluations in history.")
return
for idx, history_data in enumerate(data):
print(f"Submission #{idx} at {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(history_data['timestamp']))}, score {history_data['score']}")
print("Specify a submission number to display.")
return

if data is not None:
display_submission_info(data, args.verbose)
else:
print("Evaluation not found in history.")



if __name__ == '__main__':
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(dest='command', help='Command to execute')

# Create the parser for the "submit" command
parser_submit = subparser.add_parser('submit', help='Submit a file')
parser_submit.add_argument('file_path', help='Path to the file')
parser_submit.add_argument('--language', help='Language name', required=True)
parser_submit.add_argument('--platform', help='Platform name')
parser_submit.add_argument('--token', help='Token')
parser_submit.add_argument('-v', '--verbose', action='store_true', help='Display verbose output')

# Create the parser for the "display" command
parser_display = subparser.add_parser('display', help='Display information about a previous evaluation')
parser_display.add_argument('idx', type=int, nargs='?', help='Index of the evaluation to display')
parser_display.add_argument('-v', '--verbose', action='store_true', help='Display verbose output')

args = parser.parse_args()

if args.command == 'submit':
process_submit(args)
elif args.command == 'display':
process_display(args)
else:
parser.print_help()
2 changes: 2 additions & 0 deletions frontend/lang/en-US.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,8 @@ module.exports = {
SUBMISSION_RESULT_OUTPUT_PROGRAM: "Your program displayed:",
SUBMISSION_RESULT_OUTPUT_EXPECTED: "While the expected answer was:",
SUBMISSION_RESULT_OUTPUT_HELP: "To help you, the first differing character is highlighted in red.<br><br>Check that you display exactly what is required and nothing else; check newlines as well. Output comparison may ignore punctuation and case.",
LOCAL_WORK_DESCRIPTION: "You can work on this task locally. To do so, download the fioi.py script below and use it to submit your code from the command-line.",
LOCAL_WORK_DOWNLOAD_BUTTON: "Download the fioi.py script",
LOCAL_WORK_URL: "Use this command line to submit:",
COPIED: "Copied!",
};
2 changes: 2 additions & 0 deletions frontend/lang/fr-FR.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ module.exports = {
SUBMISSION_RESULT_OUTPUT_PROGRAM: "Votre programme a affiché :",
SUBMISSION_RESULT_OUTPUT_EXPECTED: "Alors que la réponse attendue était :",
SUBMISSION_RESULT_OUTPUT_HELP: "Pour vous aider, le premier caractère différent est mis sur fond rouge.<br><br>Vérifiez bien que vous affichez exactement ce qui est demandé et rien de plus. Vérifiez aussi les retours à la ligne.",
LOCAL_WORK_DESCRIPTION: "Vous pouvez travailler sur cette tâche depuis votre ordinateur. Pour cela, téléchargez le script fioi.py ci-dessous puis utilisez-le pour soumettre votre code depuis la ligne de commande.",
LOCAL_WORK_DOWNLOAD_BUTTON: "Télécharger le script fioi.py",
LOCAL_WORK_URL: "Utilisez cette commande pour soumettre :",
COPIED: "Copié !",
};
13 changes: 11 additions & 2 deletions frontend/task/LocalWorkDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Button, ControlGroup, Dialog, FormGroup, InputGroup} from "@blueprintjs/core";
import { Button, ControlGroup, Dialog, FormGroup, Icon, InputGroup } from "@blueprintjs/core";
import React, {useRef, useState} from "react";
import {getMessage} from "../lang";
import {IconNames} from '@blueprintjs/icons';
Expand All @@ -15,7 +15,7 @@ export function LocalWorkDialog(props: LocalWorkDialogProps) {
const taskToken = useAppSelector(state => state.platform.taskToken);
const platformName = useAppSelector(state => state.platform.platformName);
const platform = useAppSelector(selectActiveBufferPlatform);
const shellCommand = `fioi submit code.${platformsList[platform].extension} --platform ${platformName} --token ${taskToken}`;
const shellCommand = `./fioi.py submit program.${platformsList[platform].extension} --language ${platform} --platform ${platformName} --token ${taskToken}`;
const [copied, setCopied] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout>();

Expand Down Expand Up @@ -57,6 +57,15 @@ export function LocalWorkDialog(props: LocalWorkDialogProps) {
isCloseButtonShown={true}
>
<div className='bp4-dialog-body'>
<FormGroup>
<div id='localWorkExplanation'>
{getMessage('LOCAL_WORK_DESCRIPTION')}
</div>
<a href="assets/fioi.py" download>
<Icon icon="download" style={{ height: '20px' }} />
{getMessage('LOCAL_WORK_DOWNLOAD_BUTTON')}
</a>
</FormGroup>
<FormGroup labelFor='shellCommand' label={getMessage('LOCAL_WORK_URL')}>
<ControlGroup>
<InputGroup
Expand Down
7 changes: 6 additions & 1 deletion frontend/task/MenuTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {bufferDownload, bufferReload} from '../buffers/buffer_actions';
import {isServerTask} from './task_types';
import {LocalWorkDialog} from './LocalWorkDialog';
import {IconNames} from '@blueprintjs/icons';
import { SmartContractLib } from './libs/smart_contract/smart_contract_lib';

export function MenuTask() {
const recordingEnabled = useAppSelector(state => state.task.recordingEnabled);
Expand All @@ -48,6 +49,10 @@ export function MenuTask() {

const controls = useAppSelector(state => state.options.controls);

const context = quickAlgoLibraries.getContext(null, 'main');
// Display the local work button by default for smart contract tasks
const canLocalWork = serverTask && ('localwork' in controls || (context && context instanceof SmartContractLib));

const wrapperRef = useRef<HTMLDivElement>();
const dispatch = useDispatch();

Expand Down Expand Up @@ -145,7 +150,7 @@ export function MenuTask() {
</div>
</React.Fragment>
}
{serverTask && <div className="menu-item" onClick={() => setLocalWorkOpen(!localWorkOpen)}>
{canLocalWork && <div className="menu-item" onClick={() => setLocalWorkOpen(!localWorkOpen)}>
<Icon icon={IconNames.Console}/>
<span>{getMessage('MENU_LOCAL')}</span>
</div>}
Expand Down

0 comments on commit a049ea0

Please sign in to comment.