Skip to content

Commit da56d3b

Browse files
authored
submitの際のコマンド実行を追加 (#234)
* submitの際のコマンド実行を追加 * submit_filenameがない場合はFalseを返すように変更 * test_indent_estimationをaddしわすれていた * コミットするブランチを間違えた * submit.pyを修正 * USER_CONFIG_PATHを読み込んだログを出力するように * exec_on_submitのテストを追加 * テストを追加 * test_exec_on_submitにおいてtemp_dirで実行するように変更
1 parent 949b695 commit da56d3b

File tree

10 files changed

+167
-22
lines changed

10 files changed

+167
-22
lines changed

atcodertools/config/config.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
from argparse import Namespace
2-
from typing import TextIO, Dict, Any, Optional
2+
from typing import TextIO, Dict, Any, Set, Optional
33
from enum import Enum
44

5+
import os
6+
from os.path import expanduser
57
import toml
68

9+
from atcodertools.config.compiler_config import CompilerConfig
710
from atcodertools.codegen.code_style_config import CodeStyleConfig
811
from atcodertools.config.etc_config import EtcConfig
912
from atcodertools.config.postprocess_config import PostprocessConfig
1013
from atcodertools.config.tester_config import TesterConfig
11-
from atcodertools.config.compiler_config import CompilerConfig
14+
from atcodertools.config.submit_config import SubmitConfig
15+
16+
17+
USER_CONFIG_PATH = os.path.join(expanduser("~"), ".atcodertools.toml")
1218

1319

1420
class ConfigType(Enum):
1521
CODESTYLE = "codestyle"
1622
POSTPROCESS = "postprocess"
1723
TESTER = "tester"
24+
SUBMIT = "submit"
1825
ETC = "etc"
1926
COMPILER = "compiler"
2027

@@ -46,16 +53,17 @@ def __init__(self,
4653
code_style_config: CodeStyleConfig = CodeStyleConfig(),
4754
postprocess_config: PostprocessConfig = PostprocessConfig(),
4855
tester_config: TesterConfig = TesterConfig(),
56+
submit_config: SubmitConfig = SubmitConfig(),
4957
etc_config: EtcConfig = EtcConfig(),
50-
compiler_config: CompilerConfig = CompilerConfig()
5158
):
5259
self.code_style_config = code_style_config
5360
self.postprocess_config = postprocess_config
5461
self.tester_config = tester_config
62+
self.submit_config = submit_config
5563
self.etc_config = etc_config
5664

5765
@classmethod
58-
def load(cls, fp: TextIO, get_config_type, args: Optional[Namespace] = None, lang=None):
66+
def load(cls, fp: TextIO, get_config_type: Set[ConfigType], args: Optional[Namespace] = None, lang=None):
5967
"""
6068
:param fp: .toml file's file pointer
6169
:param args: command line arguments
@@ -94,6 +102,15 @@ def load(cls, fp: TextIO, get_config_type, args: Optional[Namespace] = None, lan
94102
compile_only_when_diff_detected=args.compile_only_when_diff_detected,
95103
compile_command=args.compile_command))
96104
result.tester_config = TesterConfig(**tester_config_dic)
105+
if ConfigType.SUBMIT in get_config_type:
106+
submit_config_dic = get_config_dic(
107+
config_dic, ConfigType.SUBMIT, lang)
108+
if args:
109+
submit_config_dic = _update_config_dict(submit_config_dic,
110+
dict(exec_before_submit=args.exec_before_submit,
111+
exec_after_submit=args.exec_after_submit,
112+
submit_filename=args.submit_filename))
113+
result.submit_config = SubmitConfig(**submit_config_dic)
97114
if ConfigType.ETC in get_config_type:
98115
etc_config_dic = get_config_dic(config_dic, ConfigType.ETC)
99116
if args:
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class SubmitConfig:
2+
3+
def __init__(self,
4+
exec_before_submit: str = None,
5+
exec_after_submit: str = None,
6+
submit_filename: str = None
7+
):
8+
self.exec_before_submit = exec_before_submit
9+
self.exec_after_submit = exec_after_submit
10+
self.submit_filename = submit_filename

atcodertools/tools/submit.py

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@
1111
from atcodertools.client.atcoder import AtCoderClient, LoginError
1212
from atcodertools.tools import tester
1313
from atcodertools.common.logging import logger
14+
from atcodertools.config.config import Config, ConfigType, USER_CONFIG_PATH
1415

1516
from atcodertools.tools.models.metadata import Metadata
17+
from atcodertools.tools import get_default_config_path
18+
from atcodertools.executils.run_command import run_command
1619

1720

18-
def main(prog, args, credential_supplier=None, use_local_session_cache=True) -> bool:
21+
def main(prog, args, credential_supplier=None, use_local_session_cache=True, client=None) -> bool:
1922
parser = argparse.ArgumentParser(
2023
prog=prog,
2124
formatter_class=argparse.RawTextHelpFormatter)
@@ -67,25 +70,63 @@ def main(prog, args, credential_supplier=None, use_local_session_cache=True) ->
6770
type=float,
6871
default=None)
6972

70-
args = parser.parse_args(args)
73+
parser.add_argument('--exec-before-submit',
74+
help='exec command before submit:'
75+
' [Default] None',
76+
type=str,
77+
default=None)
78+
79+
parser.add_argument('--exec-after-submit',
80+
help='run command after submit:'
81+
' [Default] None',
82+
type=str,
83+
default=None)
84+
85+
parser.add_argument('--submit-filename',
86+
help='file for submit will changed to this name:'
87+
' [Default] None',
88+
type=str,
89+
default=None)
7190

91+
parser.add_argument("--config",
92+
help="File path to your config file\n{0}{1}".format("[Default (Primary)] {}\n".format(
93+
USER_CONFIG_PATH),
94+
"[Default (Secondary)] {}\n".format(
95+
get_default_config_path())),
96+
type=str,
97+
default=None)
98+
99+
args = parser.parse_args(args)
100+
if args.config is None:
101+
if os.path.exists(USER_CONFIG_PATH):
102+
args.config = USER_CONFIG_PATH
103+
logger.info(
104+
f"config is loaded from USER_CONFIG_PATH({USER_CONFIG_PATH})")
105+
else:
106+
args.config = get_default_config_path()
107+
logger.info(
108+
f"No USER_CONFIG_PATH({USER_CONFIG_PATH}). Default config path({args.config}) is laoded. ")
72109
metadata_file = os.path.join(args.dir, "metadata.json")
110+
73111
try:
74112
metadata = Metadata.load_from(metadata_file)
75113
except IOError:
76114
logger.error(
77115
"{0} is not found! You need {0} to use this submission functionality.".format(metadata_file))
78116
return False
79117

80-
try:
81-
client = AtCoderClient()
82-
client.login(save_session_cache=not args.save_no_session_cache,
83-
credential_supplier=credential_supplier,
84-
use_local_session_cache=use_local_session_cache,
85-
)
86-
except LoginError:
87-
logger.error("Login failed. Try again.")
88-
return False
118+
with open(args.config, "r") as f:
119+
config = Config.load(f, {ConfigType.SUBMIT}, args, metadata.lang.name)
120+
if client is None:
121+
try:
122+
client = AtCoderClient()
123+
client.login(save_session_cache=not args.save_no_session_cache,
124+
credential_supplier=credential_supplier,
125+
use_local_session_cache=use_local_session_cache,
126+
)
127+
except LoginError:
128+
logger.error("Login failed. Try again.")
129+
return False
89130

90131
tester_args = []
91132
if args.exec:
@@ -100,8 +141,9 @@ def main(prog, args, credential_supplier=None, use_local_session_cache=True) ->
100141
tester_args += ["-v", str(args.error_value)]
101142

102143
if args.force or tester.main("", tester_args):
103-
submissions = client.download_submission_list(metadata.problem.contest)
104144
if not args.unlock_safety:
145+
submissions = client.download_submission_list(
146+
metadata.problem.contest)
105147
for submission in submissions:
106148
if submission.problem_id == metadata.problem.problem_id:
107149
logger.error(with_color("Cancel submitting because you already sent some code to the problem. Please "
@@ -110,9 +152,18 @@ def main(prog, args, credential_supplier=None, use_local_session_cache=True) ->
110152
return False
111153

112154
code_path = args.code or os.path.join(args.dir, metadata.code_filename)
155+
156+
if config.submit_config.exec_before_submit:
157+
run_command(config.submit_config.exec_before_submit, args.dir)
158+
if not config.submit_config.submit_filename:
159+
logger.error("submit_filename is not specified")
160+
return False
161+
code_path = config.submit_config.submit_filename
162+
logger.info(f"changed to submitfile: {code_path}")
163+
113164
for encoding in ['utf8', 'utf-8_sig', 'cp932']:
114165
try:
115-
with open(code_path, 'r', encoding=encoding) as f:
166+
with open(os.path.join(args.dir, code_path), 'r', encoding=encoding) as f:
116167
source = f.read()
117168
break
118169
except UnicodeDecodeError:
@@ -124,6 +175,8 @@ def main(prog, args, credential_supplier=None, use_local_session_cache=True) ->
124175
logger.info("{} {}".format(
125176
with_color("Done!", Fore.LIGHTGREEN_EX),
126177
metadata.problem.contest.get_submissions_url(submission)))
178+
if config.submit_config.exec_after_submit:
179+
run_command(config.submit_config.exec_after_submit, args.dir)
127180

128181
return True
129182

atcodertools/tools/tester.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import sys
88
from pathlib import Path
99
from typing import List, Tuple, Optional
10-
from os.path import expanduser
1110

1211
from colorama import Fore
1312

@@ -19,15 +18,12 @@
1918
from atcodertools.tools.models.metadata import Metadata, DEFAULT_METADATA
2019
from atcodertools.tools.utils import with_color
2120
from atcodertools.tools.compiler import compile_main_and_judge_programs, BadStatusCodeException
22-
from atcodertools.config.config import Config, ConfigType
21+
from atcodertools.config.config import Config, ConfigType, USER_CONFIG_PATH
2322
from atcodertools.tools import get_default_config_path
2423

2524
DEFAULT_EPS = 0.000000001
2625

2726

28-
USER_CONFIG_PATH = os.path.join(expanduser("~"), ".atcodertools.toml")
29-
30-
3127
class NoExecutableFileError(Exception):
3228
pass
3329

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submit]
2+
exec_before_submit='bash exec_before_submit.sh'
3+
submit_filename='exec_before_submit_is_completed'
4+
exec_after_submit='bash exec_after_submit.sh'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
touch exec_after_submit_is_completed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
echo Kyuuridenamida > exec_before_submit_is_completed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include<iostream>
2+
3+
using namespace std;
4+
5+
int main(){
6+
return 0;
7+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"code_filename": "main.cpp",
3+
"judge": {
4+
"judge_type": "normal"
5+
},
6+
"lang": "cpp",
7+
"problem": {
8+
"alphabet": "A",
9+
"contest": {
10+
"contest_id": "abc215"
11+
},
12+
"problem_id": "abc215_a"
13+
},
14+
"sample_in_pattern": "in_*.txt",
15+
"sample_out_pattern": "out_*.txt"
16+
}

tests/test_atcoder_client_mock.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import os
22
import tempfile
33
import unittest
4+
import shutil
45
from typing import Dict
56

67
from atcodertools.client.atcoder import AtCoderClient
78
from atcodertools.client.models.contest import Contest
89
from atcodertools.client.models.problem import Problem
910
from atcodertools.common.language import CPP
11+
from atcodertools.tools import submit
1012

1113
RESOURCE_DIR = os.path.join(
1214
os.path.dirname(os.path.abspath(__file__)),
@@ -119,6 +121,44 @@ def test_check_logging_in_fail(self):
119121
)
120122
self.assertFalse(self.client.check_logging_in())
121123

124+
@restore_client_after_run
125+
def test_exec_on_submit(self):
126+
global submitted_source_code
127+
submitted_source_code = None
128+
129+
def create_fake_request_func_for_source(get_url_to_resp: Dict[str, MockResponse] = None,
130+
post_url_to_resp: Dict[str,
131+
MockResponse] = None,
132+
):
133+
global submitted_source_code
134+
135+
def func(url, method="GET", **kwargs):
136+
global submitted_source_code
137+
if method == "GET":
138+
return get_url_to_resp.get(url)
139+
submitted_source_code = kwargs["data"]['sourceCode']
140+
return post_url_to_resp.get(url)
141+
return func
142+
143+
contest = Contest("abc215")
144+
145+
self.client._request = create_fake_request_func_for_source(
146+
{contest.get_submit_url(): fake_resp("submit/after_get.html")},
147+
{contest.get_submit_url(): fake_resp("submit/after_post.html")}
148+
)
149+
150+
test_dir = os.path.join(self.temp_dir, "exec_on_submit")
151+
shutil.copytree(os.path.join(RESOURCE_DIR, "exec_on_submit"), test_dir)
152+
config_path = os.path.join(test_dir, "config.toml")
153+
154+
submit.main('', ["--dir", test_dir, "--config",
155+
config_path, "-f", "-u"], client=self.client)
156+
self.assertTrue(os.path.exists(os.path.join(
157+
test_dir, "exec_before_submit_is_completed")))
158+
self.assertEqual(submitted_source_code, "Kyuuridenamida\n")
159+
self.assertTrue(os.path.exists(os.path.join(
160+
test_dir, "exec_after_submit_is_completed")))
161+
122162

123163
if __name__ == "__main__":
124164
unittest.main()

0 commit comments

Comments
 (0)