Skip to content

Commit 8cf5344

Browse files
authored
Add flag to choose template (#269)
* Add flag to choose template This change allows providing a custom template arg flag -t/--template: ``` sinol-make init foo -t [repo link or disk path] [optional subdir] ``` It defaults to the upstream repository and example_package subdir. Changed the example_package id pattern from `abc` to `__ID__`. All filenames and file contents are now substituted. Changed the positional output directory argument to `-o/--output` flag to help avoid accidents. Added result directory cleanup on failure. * Test quick fix #1 * Always cleanup tmpdir * Add other git url schemes * One more used_tmpdir tweak * Pacify tests #2 * Leave old 'abc' template string for now * Test without connecting to github Try accessing the local git repo in the project root first when cloning example_package. Only if that doesn't work, fall back to online github. Also adds some extra local path tests to init/test_unit. Also removes one unnecessary os.getcwd() call. Also removes the old "abc" template string hack since we test the current template now. * Fall back on copying local directory instead of online github
1 parent 3322119 commit 8cf5344

File tree

9 files changed

+106
-41
lines changed

9 files changed

+106
-41
lines changed

example_package/config.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,20 @@ override_limits:
4343
# Each language can have different extra arguments.
4444

4545
# extra_compilation_args:
46-
# cpp: 'abclib.cpp'
46+
# cpp: '__ID__lib.cpp'
4747

4848
# The arguments can also be in an array:
4949

5050
# extra_compilation_args:
5151
# cpp:
52-
# - 'abclib.cpp'
53-
# - 'abclib2.cpp'
52+
# - '__ID__lib.cpp'
53+
# - '__ID__lib2.cpp'
5454

5555
# Additional files used in compilation can be defined in `extra_compilation_files` key.
5656
# They are copied to the directory where the source code is compiled.
5757
# All languages have the same additional files.
5858

59-
# extra_compilation_files: ['abclib.cpp', 'abclib.py']
59+
# extra_compilation_files: ['__ID__lib.cpp', '__ID__lib.py']
6060

6161

6262
### Keys used by sinol-make:
@@ -65,7 +65,7 @@ override_limits:
6565
# The names of files in `prog/`, `doc/`, `in/` and `out/` directories have to start with this task id.
6666
# This key is only used by `sinol-make`: running `sinol-make export` creates
6767
# an archive with the proper name, which sio2 uses as the task id.
68-
sinol_task_id: abc
68+
sinol_task_id: __ID__
6969

7070
# sinol-make can behave differently depending on the value of `sinol_contest_type` key.
7171
# Mainly, it affects how points are calculated.
@@ -76,7 +76,7 @@ sinol_contest_type: oi
7676
# You can specify which tests are static (handwritten). This allows sinol-make to differentiate between
7777
# old and handwritten tests. If this key is not present old tests won't be removed.
7878
# This key is optional and should be a list of tests.
79-
sinol_static_tests: ["abc0.in", "abc0a.in"]
79+
sinol_static_tests: ["__ID__0.in", "__ID__0a.in"]
8080

8181
# sinol-make can check if the solutions run as expected when using `run` command.
8282
# Key `sinol_expected_scores` defines expected scores for each solution on each tests.
File renamed without changes.

example_package/prog/abcingen.cpp example_package/prog/__ID__ingen.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using namespace std;
44

55
// Change this function to generate one test for stresstesting.
6-
// The script prog/abcingen.sh in 10 seconds generates
6+
// The script prog/__ID__ingen.sh in 10 seconds generates
77
// as much tests as possible and compares the outputs
88
// of the model solution and brute solution.
99
// The tests shouldn't be very big, but should be able to cover edge cases.
@@ -12,7 +12,7 @@ void generate_one_stresstest(oi::Random &rng) {
1212
}
1313

1414
// Change this function to create a test with the given name.
15-
// The lists of tests to generate needs to be written in prog/abcingen.sh
15+
// The lists of tests to generate needs to be written in prog/__ID__ingen.sh
1616
void generate_proper_test(string test_name, oi::Random &rng) {
1717
if (test_name == "0a")
1818
cout << "0 1" << endl;
@@ -34,7 +34,7 @@ int main(int argc, char *argv[]) {
3434
return 0;
3535
}
3636
if (argc != 2) {
37-
cerr << "Run prog/abcingen.sh to stresstest and create proper tests." << endl;
37+
cerr << "Run prog/__ID__ingen.sh to stresstest and create proper tests." << endl;
3838
exit(1);
3939
}
4040
string test_name = argv[1];
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/sinol_make/commands/init/__init__.py

+51-26
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ class Command(BaseCommand):
1313
Class for "init"
1414
"""
1515

16+
TEMPLATE_ID='__ID__'
17+
DEFAULT_TEMPLATE = 'https://github.com/sio2project/sinol-make.git'
18+
DEFAULT_SUBDIR = 'example_package'
19+
1620
def get_name(self):
1721
return "init"
1822

@@ -23,21 +27,32 @@ def configure_subparser(self, subparser: argparse.ArgumentParser):
2327
description='Create package from predefined template with given id.'
2428
)
2529
parser.add_argument('task_id', type=str, help='id of the task to create')
26-
parser.add_argument('directory', type=str, nargs='?',
30+
parser.add_argument('-o', '--output', type=str,
2731
help='destination directory to copy the template into, defaults to task_id')
2832
parser.add_argument('-f', '--force', action='store_true',
2933
help='overwrite files in destination directory if they already exist')
34+
parser.add_argument('-t', '--template', nargs='+', default=[self.DEFAULT_TEMPLATE, self.DEFAULT_SUBDIR],
35+
help='specify template repository or directory, optionally subdirectory after space'
36+
f' (default: {self.DEFAULT_TEMPLATE} {self.DEFAULT_SUBDIR})')
37+
parser.add_argument('-v', '--verbose', action='store_true')
3038
return parser
3139

32-
def download_template(self):
33-
repo = 'https://github.com/sio2project/sinol-make.git'
34-
package_dir = 'sinol-make/example_package'
35-
self.used_tmpdir = tempfile.TemporaryDirectory()
36-
tmp_dir = self.used_tmpdir.name
37-
ret = subprocess.run(['git', 'clone', '-q', '--depth', '1', repo], cwd=tmp_dir)
38-
if ret.returncode != 0:
39-
util.exit_with_error("Could not access repository. Please try again.")
40-
path = os.path.join(tmp_dir, package_dir)
40+
def download_template(self, tmpdir, template_paths = [DEFAULT_TEMPLATE, DEFAULT_SUBDIR], verbose = False):
41+
template = template_paths[0]
42+
subdir = template_paths[1] if len(template_paths) > 1 else ''
43+
44+
is_url = template.startswith(('http://', 'https://', 'ssh://', 'git@', 'file://'))
45+
print(('Cloning' if is_url else 'Copying') + ' template ' +
46+
(f'{subdir} from {template}' if subdir else f'{template}'))
47+
if is_url:
48+
ret = subprocess.run(['git', 'clone', '-v' if verbose else '-q', '--depth', '1', template, tmpdir])
49+
if ret.returncode != 0:
50+
util.exit_with_error("Could not access repository. Please try again.")
51+
path = os.path.join(tmpdir, subdir)
52+
else:
53+
path = os.path.join(tmpdir, 'template')
54+
shutil.copytree(os.path.join(template, subdir), path)
55+
4156
if os.path.exists(os.path.join(path, '.git')):
4257
shutil.rmtree(os.path.join(path, '.git'))
4358
return path
@@ -55,22 +70,29 @@ def move_folder(self):
5570
raise
5671
for file in files:
5772
dest_filename = file
58-
if file[:3] == 'abc':
59-
dest_filename = self.task_id + file[3:]
73+
if file[:len(self.TEMPLATE_ID)] == self.TEMPLATE_ID:
74+
dest_filename = self.task_id + file[len(self.TEMPLATE_ID):]
6075
shutil.move(os.path.join(root, file), os.path.join(mapping[root], dest_filename))
6176

62-
def update_config(self):
63-
with open(os.path.join(os.getcwd(), 'config.yml')) as config:
64-
config_data = config.read()
65-
config_data = config_data.replace('sinol_task_id: abc', f'sinol_task_id: {self.task_id}')
77+
def update_task_id(self):
78+
for root, dirs, files in os.walk(os.getcwd()):
79+
for file in files:
80+
path = os.path.join(os.getcwd(), root, file)
81+
with open(path) as file:
82+
try:
83+
file_data = file.read()
84+
except UnicodeDecodeError:
85+
# ignore non-text files
86+
continue
87+
file_data = file_data.replace(self.TEMPLATE_ID, self.task_id)
6688

67-
with open(os.path.join(os.getcwd(), 'config.yml'), 'w') as config:
68-
config.write(config_data)
89+
with open(path, 'w') as file:
90+
file.write(file_data)
6991

7092
def run(self, args: argparse.Namespace):
7193
self.task_id = args.task_id
7294
self.force = args.force
73-
destination = args.directory or self.task_id
95+
destination = args.output or self.task_id
7496
if not os.path.isabs(destination):
7597
destination = os.path.join(os.getcwd(), destination)
7698
try:
@@ -80,13 +102,16 @@ def run(self, args: argparse.Namespace):
80102
util.exit_with_error(f"Destination {destination} already exists. "
81103
f"Provide a different task id or directory name, "
82104
f"or use the --force flag to overwrite.")
83-
os.chdir(destination)
84-
85-
self.template_dir = self.download_template()
105+
with tempfile.TemporaryDirectory() as tmpdir:
106+
try:
107+
self.template_dir = self.download_template(tmpdir, args.template, args.verbose)
86108

87-
self.move_folder()
88-
self.update_config()
109+
os.chdir(destination)
89110

90-
self.used_tmpdir.cleanup()
111+
self.move_folder()
112+
self.update_task_id()
91113

92-
print(util.info(f'Successfully created task "{self.task_id}"'))
114+
print(util.info(f'Successfully created task "{self.task_id}"'))
115+
except:
116+
shutil.rmtree(destination)
117+
raise

tests/commands/init/test_integration.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,25 @@
77

88

99
@pytest.mark.parametrize("temp_workdir", [''], indirect=True)
10-
def test_simple(capsys, temp_workdir):
10+
def test_init_clones_default_template(capsys, request, temp_workdir):
1111
"""
1212
Test `init` command.
1313
"""
1414
parser = configure_parsers()
15-
args = parser.parse_args(["init", "xyz"])
15+
args = ["init", "xyz"]
16+
17+
# try to avoid connecting to github when cloning example_package
18+
rootdir = str(request.config.rootdir)
19+
git_dir = os.path.join(rootdir, '.git')
20+
if os.path.exists(git_dir):
21+
git_local_url = os.path.join('file://', rootdir)
22+
args.extend(["-t", git_local_url, "example_package"])
23+
else:
24+
# copy from root directory instead of cloning
25+
# if needed we could take a dependency on gitpython and mock up a repo
26+
args.extend(["-t", rootdir, "example_package"])
27+
28+
args = parser.parse_args(args)
1629
command = Command()
1730
command.run(args)
1831
out = capsys.readouterr().out
@@ -23,7 +36,7 @@ def test_simple(capsys, temp_workdir):
2336

2437
for file in expected_files:
2538
assert os.path.isfile(os.path.join(os.getcwd(), file))
26-
39+
2740
# Check if task id is correctly set
2841
with open(os.path.join(os.getcwd(), 'config.yml')) as config_file:
2942
config_file_data = config_file.read()

tests/commands/init/test_unit.py

+30-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,36 @@
11
import os
2+
import tempfile
3+
import pytest
24

35
from sinol_make.commands.init import Command
46

57

6-
def test_if_download_successful():
8+
def copy_template(rootdir):
9+
template_path = [rootdir, Command.DEFAULT_SUBDIR]
710
command = Command()
8-
tmp_dir = command.download_template()
9-
assert os.path.isfile(os.path.join(tmp_dir,'config.yml'))
11+
with tempfile.TemporaryDirectory() as tmpdir:
12+
tmp_dir = command.download_template(tmpdir, template_path)
13+
assert os.path.isfile(os.path.join(tmp_dir, 'config.yml'))
14+
15+
16+
def test_clones_default_template(request):
17+
# try to avoid connecting to github when cloning example_package
18+
rootdir = str(request.config.rootdir)
19+
git_dir = os.path.join(rootdir, '.git')
20+
if not os.path.exists(git_dir):
21+
# if needed we could take a dependency on gitpython and mock up a repo
22+
pytest.skip(f".git not found in rootdir {rootdir}")
23+
24+
git_local_url = os.path.join('file://', rootdir)
25+
copy_template(git_local_url)
26+
27+
28+
def test_copies_local_template_absolute_path(request):
29+
rootdir_absolute = str(request.config.rootdir)
30+
copy_template(rootdir_absolute)
31+
32+
33+
def test_copies_local_template_relative_path(request):
34+
os.chdir(os.path.join(request.config.rootdir, '..'))
35+
rootdir_relative = os.path.relpath(request.config.rootdir, os.getcwd())
36+
copy_template(rootdir_relative)

0 commit comments

Comments
 (0)