11import json
22import os
33import pathlib
4+ import sys
45import textwrap
6+ from dataclasses import dataclass
7+ from typing import Dict , List , Literal , Optional
58
6- import yaml
79
10+ @dataclass
11+ class ExerciseConfig :
12+ @dataclass
13+ class ExerciseRepoConfig :
14+ repo_type : Literal ["local" , "remote" ]
15+ repo_name : str
16+ repo_title : Optional [str ]
17+ create_fork : Optional [bool ]
18+ init : Optional [bool ]
819
9- def main ():
20+ exercise_name : str
21+ tags : List [str ]
22+ requires_git : bool
23+ requires_github : bool
24+ base_files : Dict [str , str ]
25+ exercise_repo : ExerciseRepoConfig
26+
27+ def to_json (self ) -> str :
28+ return json .dumps (self , default = lambda o : o .__dict__ , sort_keys = False , indent = 2 )
29+
30+ @property
31+ def exercise_dir (self ) -> pathlib .Path :
32+ cur_path = pathlib .Path (os .getcwd ())
33+ exercise_dir_name = self .exercise_name .replace ("-" , "_" )
34+ exercise_dir = cur_path / exercise_dir_name
35+ return exercise_dir
36+
37+
38+ def confirm (prompt : str , default : bool ) -> bool :
39+ str_result = input (f"{ prompt } (defaults to { 'y' if default else 'N' } ) [y/N]: " )
40+ bool_value = default if str_result .strip () == "" else str_result .lower () == "y"
41+ return bool_value
42+
43+
44+ def prompt (prompt : str , default : str ) -> str :
45+ str_result = input (f"{ prompt } (defaults to '{ default } '): " )
46+ if str_result .strip () == "" :
47+ return default
48+ return str_result .strip ()
49+
50+
51+ def get_exercise_config () -> ExerciseConfig :
1052 exercise_name = input ("Exercise name: " )
1153 tags_str = input ("Tags (space separated): " )
1254 tags = [] if tags_str .strip () == "" else tags_str .split (" " )
13- requires_repo_str = input ("Requires repo? (defaults to y) y/N: " )
14- requires_repo = (
15- requires_repo_str .strip () == ""
16- or requires_repo_str == "y"
17- or requires_repo_str == "Y"
18- )
19- requires_github_str = input ("Requires Github? (defaults to y) y/N: " )
20- requires_github = (
21- requires_github_str .strip () == ""
22- or requires_github_str == "y"
23- or requires_github_str == "Y"
55+ requires_git = confirm ("Requires Git?" , True )
56+ requires_github = confirm ("Requires Github?" , True )
57+ exercise_repo_type = prompt ("Exercise repo type (local or remote)" , "local" ).lower ()
58+
59+ if exercise_repo_type != "local" and exercise_repo_type != "remote" :
60+ print ("Invalid exercise_repo_type, only local and remote allowed" )
61+ sys .exit (1 )
62+
63+ exercise_repo_name = prompt ("Exercise repo name" , exercise_name .replace ("-" , "_" ))
64+
65+ init : Optional [bool ] = None
66+ create_fork : Optional [bool ] = None
67+ repo_title : Optional [str ] = None
68+ if exercise_repo_type == "local" :
69+ init = confirm ("Initialize exercise repo as Git repository?" , True )
70+ elif exercise_repo_type == "remote" :
71+ repo_title = prompt ("Git-Mastery Github repository title" , "" )
72+ create_fork = confirm ("Create fork of repository?" , True )
73+ return ExerciseConfig (
74+ exercise_name = exercise_name ,
75+ tags = tags ,
76+ requires_git = requires_git ,
77+ requires_github = requires_github ,
78+ base_files = {},
79+ exercise_repo = ExerciseConfig .ExerciseRepoConfig (
80+ repo_type = exercise_repo_type , # type: ignore
81+ repo_name = exercise_repo_name ,
82+ repo_title = repo_title ,
83+ create_fork = create_fork ,
84+ init = init ,
85+ ),
2486 )
2587
26- cur_path = pathlib .Path (os .getcwd ())
27- exercise_dir_name = exercise_name .replace ("-" , "_" )
28- exercise_dir = cur_path / exercise_dir_name
29- os .makedirs (exercise_dir )
30- with open (exercise_dir / ".gitmastery-exercise.json" , "w" ) as exercise_config_file :
31- exercise_config = {
32- "exercise_name" : exercise_name ,
33- "tags" : tags ,
34- "is_downloadable" : True ,
35- "requires_repo" : requires_repo ,
36- "requires_github" : requires_github ,
37- "resources" : {},
38- }
39- exercise_config_str = json .dumps (exercise_config , indent = 2 )
40- exercise_config_file .write (exercise_config_str )
41-
42- with open (exercise_dir / "README.md" , "w" ) as readme_file :
88+
89+ def create_exercise_config_file (config : ExerciseConfig ) -> None :
90+ with open (".gitmastery-exercise.json" , "w" ) as exercise_config_file :
91+ exercise_config_file .write (config .to_json ())
92+
93+
94+ def create_readme_file (config : ExerciseConfig ) -> None :
95+ with open ("README.md" , "w" ) as readme_file :
4396 readme = f"""
44- # { exercise_name }
97+ # { config . exercise_name }
4598
4699 <!--- Insert exercise description -->
47100
@@ -52,7 +105,7 @@ def main():
52105 ## Hints
53106
54107 <!--- Insert hints here -->
55- <!---
108+ <!---
56109 Use Github Markdown's collapsible content:
57110 <details>
58111 <summary>...</summary>
@@ -62,8 +115,10 @@ def main():
62115 """
63116 readme_file .write (textwrap .dedent (readme ).lstrip ())
64117
118+
119+ def create_download_py_file () -> None :
65120 # TODO: conditionally add the git tagging only when requires_repo is True
66- with open (exercise_dir / "download.py" , "w" ) as download_script_file :
121+ with open ("download.py" , "w" ) as download_script_file :
67122 download_script = """
68123 import subprocess
69124 from sys import exit
@@ -100,9 +155,9 @@ def setup(verbose: bool = False):
100155 """
101156 download_script_file .write (textwrap .dedent (download_script ).lstrip ())
102157
103- os .makedirs (exercise_dir / "res" , exist_ok = True )
104158
105- with open (exercise_dir / "verify.py" , "w" ) as verify_script_file :
159+ def create_verify_py_file () -> None :
160+ with open ("verify.py" , "w" ) as verify_script_file :
106161 verify_script = """
107162 from typing import List
108163
@@ -118,19 +173,25 @@ def verify(repo: GitAutograderRepo) -> GitAutograderOutput:
118173 """
119174 verify_script_file .write (textwrap .dedent (verify_script ).lstrip ())
120175
121- open (exercise_dir / "__init__.py" , "a" ).close ()
122176
123- tests_dir = exercise_dir / "tests"
177+ def create_init_py_file () -> None :
178+ open ("__init__.py" , "a" ).close ()
179+
180+
181+ def create_test_dir (config : ExerciseConfig ) -> None :
182+ tests_dir = "tests"
124183 os .makedirs (tests_dir , exist_ok = True )
125- open (tests_dir / "__init__.py" , "a" ).close ()
184+ os .chdir (tests_dir )
185+
186+ create_init_py_file ()
126187
127- with open (tests_dir / "test_verify.py" , "w" ) as test_grade_file :
188+ with open ("test_verify.py" , "w" ) as test_grade_file :
128189 test_grade = f"""
129190 from git_autograder import GitAutograderTestLoader
130191
131192 from ..verify import verify
132193
133- REPOSITORY_NAME = "{ exercise_name } "
194+ REPOSITORY_NAME = "{ config . exercise_name } "
134195
135196 loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify)
136197
@@ -141,8 +202,8 @@ def test():
141202 """
142203 test_grade_file .write (textwrap .dedent (test_grade ).lstrip ())
143204
144- os .makedirs (tests_dir / "specs" , exist_ok = True )
145- with open (tests_dir / "specs" / " base.yml" , "w" ) as base_spec_file :
205+ os .makedirs ("specs" , exist_ok = True )
206+ with open ("specs/ base.yml" , "w" ) as base_spec_file :
146207 base_spec = """
147208 initialization:
148209 steps:
@@ -154,5 +215,18 @@ def test():
154215 base_spec_file .write (textwrap .dedent (base_spec ).lstrip ())
155216
156217
218+ def main ():
219+ config = get_exercise_config ()
220+ os .makedirs (config .exercise_dir )
221+ os .chdir (config .exercise_dir )
222+
223+ os .makedirs ("res" , exist_ok = True )
224+ create_exercise_config_file (config )
225+ create_readme_file (config )
226+ create_download_py_file ()
227+ create_verify_py_file ()
228+ create_test_dir (config )
229+
230+
157231if __name__ == "__main__" :
158232 main ()
0 commit comments