11#!/usr/bin/env python3.7
22import argparse
3- from enum import Enum , auto
3+ from argparse import Namespace
4+ from enum import IntEnum , auto
45from fnmatch import fnmatch
5- import json
66import logging
77from pathlib import Path
88import shlex
99from subprocess import check_call , DEVNULL , CalledProcessError
1010import sys
11+ from typing import Dict , List , Any , Iterator
1112
1213from data import Config , ExerciseInfo
14+ from generate_tests import clone_if_missing
15+ from githelp import Repo
1316from test_exercises import check_assignment
1417
15- DEFAULT_SPEC_LOCATION = Path ('spec ' )
18+ DEFAULT_SPEC_LOCATION = Path ('.problem-specifications ' )
1619
1720logging .basicConfig (format = "%(levelname)s:%(message)s" )
1821logger = logging .getLogger ("generator" )
1922logger .setLevel (logging .WARN )
2023
2124
22- class TemplateStatus (Enum ):
25+ class TemplateStatus (IntEnum ):
2326 OK = auto ()
2427 MISSING = auto ()
2528 INVALID = auto ()
2629 TEST_FAILURE = auto ()
2730
28- def __lt__ (self , other ):
29- return self .value < other .value
3031
31-
32- def exec_cmd (cmd ):
32+ def exec_cmd (cmd : str ) -> bool :
3333 try :
3434 args = shlex .split (cmd )
3535 if logger .isEnabledFor (logging .DEBUG ):
@@ -51,7 +51,7 @@ def run_tests(exercise: ExerciseInfo) -> bool:
5151 return check_assignment (exercise , quiet = True ) == 0
5252
5353
54- def get_status (exercise : ExerciseInfo , spec_path : Path ):
54+ def get_status (exercise : ExerciseInfo , spec_path : Path ) -> TemplateStatus :
5555 if exercise .template_path .is_file ():
5656 if generate_template (exercise , spec_path ):
5757 if run_tests (exercise ):
@@ -65,6 +65,25 @@ def get_status(exercise: ExerciseInfo, spec_path: Path):
6565 return TemplateStatus .MISSING
6666
6767
68+ def set_loglevel (opts : Namespace ):
69+ if opts .quiet :
70+ logger .setLevel (logging .FATAL )
71+ elif opts .verbose >= 2 :
72+ logger .setLevel (logging .DEBUG )
73+ elif opts .verbose >= 1 :
74+ logger .setLevel (logging .INFO )
75+
76+
77+ def filter_exercises (exercises : List [ExerciseInfo ], pattern : str ) -> Iterator [ExerciseInfo ]:
78+ for exercise in exercises :
79+ if not exercise .get ("deprecated" , False ):
80+ if exercise .type == 'concept' :
81+ # Concept exercises are not generated
82+ continue
83+ if fnmatch (exercise ["slug" ], pattern ):
84+ yield exercise
85+
86+
6887if __name__ == "__main__" :
6988 parser = argparse .ArgumentParser ()
7089 parser .add_argument ("exercise_pattern" , nargs = "?" , default = "*" , metavar = "EXERCISE" )
@@ -81,51 +100,37 @@ def get_status(exercise: ExerciseInfo, spec_path: Path):
81100 ),
82101 )
83102 opts = parser .parse_args ()
84- if opts .quiet :
85- logger .setLevel (logging .FATAL )
86- elif opts .verbose >= 2 :
87- logger .setLevel (logging .DEBUG )
88- elif opts .verbose >= 1 :
89- logger .setLevel (logging .INFO )
103+ set_loglevel (opts )
90104
91105 if not opts .spec_path .is_dir ():
92106 logger .error (f"{ opts .spec_path } is not a directory" )
93107 sys .exit (1 )
94- opts .spec_path = opts .spec_path .absolute ()
95- logger .debug (f"problem-specifications path is { opts .spec_path } " )
96-
97- result = True
98- buckets = {
99- TemplateStatus .MISSING : [],
100- TemplateStatus .INVALID : [],
101- TemplateStatus .TEST_FAILURE : [],
102- }
103- config = Config .load ()
104- for exercise in filter (
105- lambda e : fnmatch (e .slug , opts .exercise_pattern ),
106- config .exercises .all ()
107- ):
108- if exercise .deprecated :
109- continue
110- if exercise .type == 'concept' :
111- # Concept exercises are not generated
112- continue
113- status = get_status (exercise , opts .spec_path )
114- if status == TemplateStatus .OK :
115- logger .info (f"{ exercise .slug } : { status .name } " )
116- else :
117- buckets [status ].append (exercise .slug )
118- result = False
119- if opts .stop_on_failure :
120- logger .error (f"{ exercise .slug } : { status .name } " )
121- break
122-
123- if not opts .quiet and not opts .stop_on_failure :
124- for status , bucket in sorted (buckets .items ()):
125- if bucket :
126- print (f"The following exercises have status '{ status .name } '" )
127- for exercise in sorted (bucket ):
128- print (f' { exercise } ' )
129-
130- if not result :
131- sys .exit (1 )
108+ with clone_if_missing (repo = Repo .ProblemSpecifications , directory = opts .spec_path ):
109+
110+ result = True
111+ buckets = {
112+ TemplateStatus .MISSING : [],
113+ TemplateStatus .INVALID : [],
114+ TemplateStatus .TEST_FAILURE : [],
115+ }
116+ config = Config .load ()
117+ for exercise in filter_exercises (config .exercises .all ()):
118+ status = get_status (exercise , opts .spec_path )
119+ if status == TemplateStatus .OK :
120+ logger .info (f"{ exercise .slug } : { status .name } " )
121+ else :
122+ buckets [status ].append (exercise .slug )
123+ result = False
124+ if opts .stop_on_failure :
125+ logger .error (f"{ exercise .slug } : { status .name } " )
126+ break
127+
128+ if not opts .quiet and not opts .stop_on_failure :
129+ for status , bucket in sorted (buckets .items ()):
130+ if bucket :
131+ print (f"The following exercises have status '{ status .name } '" )
132+ for exercise in sorted (bucket ):
133+ print (f' { exercise } ' )
134+
135+ if not result :
136+ sys .exit (1 )
0 commit comments