from datetime import datetime, timezone from enum import Enum from pathlib import Path from typing import Any, Dict, List, Type from pydantic import ( AliasChoices, BaseModel, ConfigDict, Field, field_validator, model_validator, ) from joj3_config_generator.models.common import Memory, Time from joj3_config_generator.models.const import ( DEFAULT_CASE_SCORE, DEFAULT_CPU_LIMIT, DEFAULT_FILE_LIMIT, DEFAULT_MEMORY_LIMIT, DEFAULT_PROC_LIMIT, ) class ParserResultDetail(BaseModel): cpu_time: bool = Field( True, validation_alias=AliasChoices("cpu-time", "cpu_time") ) # Display CPU time time: bool = True # Display run time mem: bool = True # Display memory usage stdout: bool = False # Display stdout messages stderr: bool = False # Display stderr messages exit_status: bool = Field( True, validation_alias=AliasChoices("exit-status", "exit_status") ) # Display exit status proc_peak: bool = Field( False, validation_alias=AliasChoices("proc-peak", "proc_peak") ) # Display peak process count error: bool = False # Display error messages class ParserFile(BaseModel): name: str = "" class ParserLog(BaseModel): filename: str msg: str = "" level: str = "" class ParserDummy(BaseModel): comment: str = "" score: int = 0 force_quit: bool = Field( False, validation_alias=AliasChoices("force-quit", "force_quit") ) class ParserKeyword(BaseModel): keyword: List[str] = [] weight: List[int] = [] class ParserDiffOutputs(BaseModel): score: int = 0 ignore_spaces: bool = Field( True, validation_alias=AliasChoices("ignore-spaces", "ignore_spaces") ) hide: bool = False force_quit: bool = Field( False, validation_alias=AliasChoices("force-quit", "force_quit") ) max_length: int = Field( 2048, validation_alias=AliasChoices("max-length", "max_length") ) max_lines: int = Field(50, validation_alias=AliasChoices("max-lines", "max_lines")) hide_common_prefix: bool = Field( False, validation_alias=AliasChoices("hide-common-prefix", "hide_common_prefix") ) class ParserDiff(BaseModel): output: ParserDiffOutputs = ParserDiffOutputs() default_score: int = Field( DEFAULT_CASE_SCORE, validation_alias=AliasChoices("default-score", "default_score"), ) class StageFiles(BaseModel): import_: List[str] = Field([], validation_alias="import") export: List[str] = [] class Limit(BaseModel): mem: int = DEFAULT_MEMORY_LIMIT cpu: int = DEFAULT_CPU_LIMIT stdout: int = DEFAULT_FILE_LIMIT stderr: int = DEFAULT_FILE_LIMIT proc: int = DEFAULT_PROC_LIMIT model_config = ConfigDict(validate_assignment=True) @field_validator("cpu", mode="before") @classmethod def ensure_time(cls, v: Any) -> Time: if isinstance(v, str): return Time(v) raise ValueError("Must be a string") @field_validator("mem", "stdout", "stderr", mode="before") @classmethod def ensure_mem(cls, v: Any) -> Memory: if isinstance(v, str): return Memory(v) raise ValueError("Must be a string") class Parser(str, Enum): CLANG_TIDY = "clangtidy" CPPCHECK = "cppcheck" CPPLINT = "cpplint" KEYWORD = "keyword" RESULT_STATUS = "result-status" RESULT_DETAIL = "result-detail" DUMMY = "dummy" FILE = "file" DIFF = "diff" ELF = "elf" class Stage(BaseModel): name: str = "" # Stage name env: List[str] = [] command: str = "" # Command to run files: StageFiles = StageFiles() in_: str = Field("", validation_alias="in") out_: str = Field("", validation_alias="out") copy_in_cwd: bool = Field( True, validation_alias=AliasChoices("copy-in-cwd", "copy_in_cwd") ) score: int = 0 parsers: List[Parser] = [] # list of parsers limit: Limit = Limit() dummy: ParserDummy = ParserDummy() result_status: ParserDummy = Field( ParserDummy(), validation_alias=AliasChoices("result-status", "result_status") ) keyword: ParserKeyword = ParserKeyword() clangtidy: ParserKeyword = ParserKeyword() cppcheck: ParserKeyword = ParserKeyword() cpplint: ParserKeyword = ParserKeyword() elf: ParserKeyword = ParserKeyword() result_detail: ParserResultDetail = Field( ParserResultDetail(), validation_alias=AliasChoices("result-detail", "result_detail"), ) file: ParserFile = ParserFile() skip: List[str] = [] # cases related cases: Dict[str, "Stage"] = {} diff: ParserDiff = ParserDiff() model_config = ConfigDict(extra="allow") @model_validator(mode="before") @classmethod def gather_cases(cls: Type["Stage"], values: Dict[str, Any]) -> Dict[str, Any]: cases = {k: v for k, v in values.items() if k.startswith("case")} limit = values.get("limit", {}) parsed_cases = {} for key, case in cases.items(): case_with_limit = {**limit, **case.get("limit", {})} case_for_parsing = {**case, "limit": case_with_limit} parsed_cases[key] = case_for_parsing values.pop(key) values["cases"] = parsed_cases return values class Release(BaseModel): end_time: datetime = Field( datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc), validation_alias=AliasChoices("end-time", "end_time"), ) # timestamp = 0, no end time begin_time: datetime = Field( datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc), validation_alias=AliasChoices("begin-time", "begin_time"), ) # timestamp = 0, no begin time class Task(BaseModel): name: str = "unknown" class Config(BaseModel): root: Path = Path(".") path: Path = Path("task.toml") task: Task = Task() # Task name (e.g., hw3 ex5) release: Release = Release() # Release configuration stages: List[Stage] = [] # list of stage configurations