Repository: online-judge-tools/template-generator

Issue:
The default template `generate.py` has syntax error when string variables exsit in input
```shell
oj-template -t generate.py https://atcoder.jp/contests/abc166/tasks/abc166_a
```
の生成コードが syntax error となっているようです。文字列が入力に与えられる他の問題でも同様でした。僕が python に不慣れなため、具体的な間違いの内容は分かりませんでした。。

Comments:

Patch:
diff --git a/onlinejudge_template/generator/python.py b/onlinejudge_template/generator/python.py
index 88075ef..5fd6303 100644
--- a/onlinejudge_template/generator/python.py
+++ b/onlinejudge_template/generator/python.py
@@ -114,11 +114,11 @@ def _generate_input_dfs(node: FormatNode, *, declared: Set[VarName], initialized
         elif type_ == VarType.Float:
             return OtherNode(line=f"""{var} = 100.0 * random.random()  # TODO: edit here""")
         elif type_ == VarType.String:
-            return OtherNode(line=f"""{var} = ''.join([random.choice('abcde') for range(random.randint(1, 100))])  # TODO: edit here""")
+            return OtherNode(line=f"""{var} = ''.join([random.choice('abcde') for _ in range(random.randint(1, 100))])  # TODO: edit here""")
         elif type_ == VarType.Char:
             return OtherNode(line=f"""{var} = random.choice('abcde')  # TODO: edit here""")
         else:
-            return OtherNode(line=f"""{var} = None  # TODO: edit here""")
+            return OtherNode(line=f"""{var} = random.randint(1, 10)  # TODO: edit here""")
     elif isinstance(node, NewlineNode):
         return SentencesNode(sentences=[])
     elif isinstance(node, SequenceNode):
diff --git a/setup.cfg b/setup.cfg
index ac26f88..ca5af62 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -14,9 +14,9 @@ classifiers =
 
 [options.extras_require]
 dev =
-    isort == 5.4.1
+    isort == 5.5.2
     mypy == 0.782
-    pylint == 2.5.3
+    pylint == 2.6.0
     yapf == 0.30.0
 doc =
     sphinx >= 2.4


Test patch:
diff --git a/tests/command_template.py b/tests/command_template.py
index 28de86f..facea02 100644
--- a/tests/command_template.py
+++ b/tests/command_template.py
@@ -13,6 +13,7 @@ from onlinejudge_template.main import main
 
 class TestOJTemplateCommand(unittest.TestCase):
     """TestOJTemplateCommand is a class for end-to-end tests about oj-template command.
+    The tests actually compile and execute the generated code and check them get AC on sample cases.
     """
     def _helper(self, *, url: str, template: str, placeholder: str, code: str, compile: Callable[[pathlib.Path], List[str]], command: Callable[[pathlib.Path], str]):
         with tempfile.TemporaryDirectory() as tmpdir_:
@@ -43,8 +44,8 @@ class TestOJTemplateCommand(unittest.TestCase):
             y = str(b) * a
             return int(min(x, y))
         """), '    ')
-        compile = lambda tmpdir: [sys.executable, '--version']
-        command = lambda tmpdir: ' '.join([sys.executable, str(tmpdir / 'main.py')])
+        compile = lambda tmpdir: [sys.executable, '--version']  # nop
+        command = lambda tmpdir: ' '.join([sys.executable, str(tmpdir / template)])
         self._helper(url=url, template=template, placeholder=placeholder, code=code, compile=compile, command=command)
 
     def test_main_cpp_aplusb(self) -> None:
@@ -54,6 +55,60 @@ class TestOJTemplateCommand(unittest.TestCase):
         code = textwrap.indent(textwrap.dedent("""\
             return A + B;
         """), '    ')
-        compile = lambda tmpdir: ['g++', '-std=c++14', str(tmpdir / 'main.cpp'), '-o', str(tmpdir / 'a.out')]
+        compile = lambda tmpdir: ['g++', '-std=c++14', str(tmpdir / template), '-o', str(tmpdir / 'a.out')]
         command = lambda tmpdir: str(tmpdir / 'a.out')
         self._helper(url=url, template=template, placeholder=placeholder, code=code, compile=compile, command=command)
+
+
+class TestOJTemplateCommandGenerator(unittest.TestCase):
+    """TestOJTemplateCommandGenerator is a class for end-to-end tests about oj-template command.
+    The tests actually executes the generator and check the result with a validator.
+    """
+    def _helper(self, *, url: str, template: str, compile: Callable[[pathlib.Path], List[str]], command: Callable[[pathlib.Path], List[str]]):
+        with tempfile.TemporaryDirectory() as tmpdir_:
+            tmpdir = pathlib.Path(tmpdir_)
+            source_file = tmpdir / template
+
+            # generate
+            with open(source_file, 'w') as fh:
+                with contextlib.redirect_stdout(fh):
+                    main(['-t', template, url])
+
+            # test
+            subprocess.check_call(compile(tmpdir), stdout=sys.stdout, stderr=sys.stderr)
+            return subprocess.check_output(command(tmpdir), stderr=sys.stderr)
+
+    def test_generate_py_arc088_b(self) -> None:
+        # arc088_b has a format with a binary string variable.
+        url = 'https://atcoder.jp/contests/arc088/tasks/arc088_b'
+        template = 'generate.py'
+        compile = lambda tmpdir: [sys.executable, '--version']  # nop
+        command = lambda tmpdir: [sys.executable, str(tmpdir / template)]
+
+        def validate(case: bytes) -> None:
+            lines = case.splitlines()
+            self.assertEqual(len(lines), 1)
+            s, = lines[0].split()
+            self.assertTrue(s.isalpha())
+
+        validate(self._helper(url=url, template=template, compile=compile, command=command))
+
+    def test_generate_py_arc089_b(self) -> None:
+        # arc089_b has a non-trivial format with char variables.
+        url = 'https://atcoder.jp/contests/arc089/tasks/arc089_b'
+        template = 'generate.py'
+        compile = lambda tmpdir: [sys.executable, '--version']  # nop
+        command = lambda tmpdir: [sys.executable, str(tmpdir / template)]
+
+        def validate(case: bytes) -> None:
+            lines = case.splitlines()
+            n, k = map(int, lines[0].split())
+            self.assertEqual(len(lines) - 1, n)
+            for line in lines[1:]:
+                x, y, c = line.split()
+                int(x)
+                int(y)
+                self.assertTrue(c.isalpha())
+                self.assertEqual(len(c), 1)
+
+        validate(self._helper(url=url, template=template, compile=compile, command=command))