WIP: dev #6
|
@ -19,35 +19,76 @@ from joj3_config_generator.lib.task import (
|
|||
get_executorWithConfig,
|
||||
)
|
||||
from joj3_config_generator.models import joj1, repo, result, task
|
||||
from joj3_config_generator.lib.repo import getHealthcheckConfig, getTeapotConfig
|
||||
from joj3_config_generator.models import (
|
||||
Cmd,
|
||||
CmdFile,
|
||||
ExecutorConfig,
|
||||
ExecutorWithConfig,
|
||||
ParserConfig,
|
||||
Repo,
|
||||
ResultConfig,
|
||||
Stage,
|
||||
StageConfig,
|
||||
Task,
|
||||
TeapotConfig,
|
||||
)
|
||||
|
||||
|
||||
# FIXME: LLM generated convert function, only for demostration
|
||||
def convert(repo_conf: repo.Config, task_conf: task.Config) -> result.Config:
|
||||
# Create the base ResultConf object
|
||||
result_conf = result.Config(
|
||||
name=task_conf.task,
|
||||
# TODO: specify the exact folder difference
|
||||
log_path=f"{task_conf.task.replace(' ', '-')}.log",
|
||||
# TODO: specify the exact folder difference
|
||||
log_path=f"{task_conf.task.replace(' ', '-')}.log",
|
||||
expire_unix_timestamp=(
|
||||
int(task_conf.release.deadline.timestamp())
|
||||
if task_conf.release.deadline
|
||||
else -1
|
||||
),
|
||||
stage=result.Stage(stages=[], sandbox_token=repo_conf.sandbox_token),
|
||||
teapot=result.Teapot(),
|
||||
stage=StageConfig(stages=[], sandbox_token=repo_conf.sandbox_token),
|
||||
teapot=getTeapotConfig(repo_conf, task_conf),
|
||||
)
|
||||
|
||||
# Construct healthcheck stage
|
||||
healthcheck_stage = getHealthcheckConfig(repo_conf)
|
||||
healthcheck_stage = getHealthcheckConfig(repo_conf, task_conf)
|
||||
result_conf.stage.stages.append(healthcheck_stage)
|
||||
cached: list[str] = []
|
||||
cached = []
|
||||
# Convert each stage in the task configuration
|
||||
for task_stage in task_conf.stages:
|
||||
executor_with_config, cached = get_executorWithConfig(task_stage, cached)
|
||||
conf_stage = get_conf_stage(task_stage, executor_with_config)
|
||||
conf_stage = fix_result_detail(task_stage, conf_stage)
|
||||
conf_stage = fix_dummy(task_stage, conf_stage)
|
||||
conf_stage = fix_keyword(task_stage, conf_stage)
|
||||
conf_stage = fix_diff(task_stage, conf_stage)
|
||||
executor_with_config = result.ExecutorWith(
|
||||
default=result.Cmd(
|
||||
args=task_stage.command.split(),
|
||||
copy_in={
|
||||
file: result.CmdFile(src=file) for file in task_stage.files.import_
|
||||
},
|
||||
copy_out_cached=task_stage.files.export,
|
||||
),
|
||||
cases=[], # You can add cases if needed
|
||||
)
|
||||
conf_stage = result.StageDetail(
|
||||
name=task_stage.name,
|
||||
group=task_conf.task,
|
||||
executor=result.Executor(
|
||||
name="sandbox",
|
||||
with_=executor_with_config,
|
||||
),
|
||||
parsers=[
|
||||
result.Parser(name=parser, with_={}) for parser in task_stage.parsers
|
||||
],
|
||||
)
|
||||
if "result-detail" in task_stage.parsers:
|
||||
result_detail_parser = next(
|
||||
p for p in conf_stage.parsers if p.name == "result-detail"
|
||||
)
|
||||
if task_stage.result_detail is not None:
|
||||
result_detail_parser.with_.update(task_stage.result_detail)
|
||||
|
||||
result_conf.stage.stages.append(conf_stage)
|
||||
|
||||
return result_conf
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
from joj3_config_generator.models.repo import Repo as Repo
|
||||
from joj3_config_generator.models.result import Cmd as Cmd
|
||||
from joj3_config_generator.models.result import CmdFile as CmdFile
|
||||
from joj3_config_generator.models.result import ExecutorConfig as ExecutorConfig
|
||||
from joj3_config_generator.models.result import ExecutorWithConfig as ExecutorWithConfig
|
||||
from joj3_config_generator.models.result import ParserConfig as ParserConfig
|
||||
from joj3_config_generator.models.result import ResultConfig as ResultConfig
|
||||
from joj3_config_generator.models.result import Stage as Stage
|
||||
from joj3_config_generator.models.result import StageConfig as StageConfig
|
||||
from joj3_config_generator.models.result import TeapotConfig as TeapotConfig
|
||||
from joj3_config_generator.models.task import Task as Task
|
|
@ -1,18 +1,40 @@
|
|||
import hashlib
|
||||
import shlex
|
||||
import socket
|
||||
from pathlib import Path
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from joj3_config_generator.models import joj1, repo, result, task
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from joj3_config_generator.models import (
|
||||
Cmd,
|
||||
CmdFile,
|
||||
ExecutorConfig,
|
||||
ExecutorWithConfig,
|
||||
ParserConfig,
|
||||
Repo,
|
||||
ResultConfig,
|
||||
Stage,
|
||||
StageConfig,
|
||||
Task,
|
||||
TeapotConfig,
|
||||
)
|
||||
|
||||
|
||||
def get_temp_directory() -> str:
|
||||
return tempfile.mkdtemp(prefix="repo-checker-")
|
||||
|
||||
|
||||
def getGradingRepoName() -> str:
|
||||
host_name = socket.gethostname()
|
||||
return f"{host_name.split('-')[0]}-joj"
|
||||
path = os.path.expanduser("~/.config/teapot/teapot.env")
|
||||
if os.path.exists(path):
|
||||
load_dotenv(path)
|
||||
repo_name = os.environ.get("GITEA_ORG_NAME")
|
||||
if repo_name is not None:
|
||||
return f"{repo_name.split('-')[0]}-joj"
|
||||
return "ece482-joj"
|
||||
|
||||
|
||||
def getTeapotConfig(repo_conf: repo.Config, task_conf: task.Config) -> result.Teapot:
|
||||
teapot = result.Teapot(
|
||||
def getTeapotConfig(repo_conf: Repo, task_conf: Task) -> TeapotConfig:
|
||||
teapot = TeapotConfig(
|
||||
# TODO: fix the log path
|
||||
log_path=f"{task_conf.task.replace(' ', '-')}-joint-teapot-debug.log",
|
||||
scoreboard_path=f"{task_conf.task.replace(' ', '-')}-scoreboard.csv",
|
||||
|
@ -22,7 +44,7 @@ def getTeapotConfig(repo_conf: repo.Config, task_conf: task.Config) -> result.Te
|
|||
return teapot
|
||||
|
||||
|
||||
def getHealthcheckCmd(repo_conf: repo.Config) -> result.Cmd:
|
||||
def getHealthcheckCmd(repo_conf: Repo) -> Cmd:
|
||||
repoSize = repo_conf.max_size
|
||||
immutable = repo_conf.files.immutable
|
||||
repo_size = f"-repoSize={str(repoSize)} "
|
||||
|
@ -38,7 +60,7 @@ def getHealthcheckCmd(repo_conf: repo.Config) -> result.Cmd:
|
|||
else:
|
||||
immutable_files = immutable_files + name + ","
|
||||
# FIXME: need to make solution and make things easier to edit with global scope
|
||||
chore = f"./repo-health-checker -root=. "
|
||||
chore = f"/{get_temp_directory}/repo-health-checker -root=. "
|
||||
args = ""
|
||||
args = args + chore
|
||||
args = args + repo_size
|
||||
|
@ -49,25 +71,27 @@ def getHealthcheckCmd(repo_conf: repo.Config) -> result.Cmd:
|
|||
|
||||
args = args + immutable_files
|
||||
|
||||
cmd = result.Cmd(
|
||||
args=shlex.split(args),
|
||||
cmd = Cmd(
|
||||
args=args.split(),
|
||||
# FIXME: easier to edit within global scope
|
||||
copy_in={
|
||||
f"./repo-health-checker": result.CmdFile(src=f"./repo-health-checker")
|
||||
f"/{get_temp_directory()}/repo-health-checker": CmdFile(
|
||||
src=f"/{get_temp_directory()}/repo-health-checker"
|
||||
)
|
||||
},
|
||||
)
|
||||
return cmd
|
||||
|
||||
|
||||
def getHealthcheckConfig(repo_conf: repo.Config) -> result.StageDetail:
|
||||
healthcheck_stage = result.StageDetail(
|
||||
def getHealthcheckConfig(repo_conf: Repo, task_conf: Task) -> Stage:
|
||||
healthcheck_stage = Stage(
|
||||
|
||||
name="healthcheck",
|
||||
group="",
|
||||
executor=result.Executor(
|
||||
executor=ExecutorConfig(
|
||||
name="sandbox",
|
||||
with_=result.ExecutorWith(default=getHealthcheckCmd(repo_conf), cases=[]),
|
||||
with_=ExecutorWithConfig(default=getHealthcheckCmd(repo_conf), cases=[]),
|
||||
),
|
||||
parsers=[result.Parser(name="healthcheck", with_={"score": 0, "comment": ""})],
|
||||
parsers=[ParserConfig(name="healthcheck", with_={"score": 0, "comment": ""})],
|
||||
)
|
||||
return healthcheck_stage
|
||||
|
||||
|
@ -81,10 +105,7 @@ def calc_sha256sum(file_path: str) -> str:
|
|||
|
||||
|
||||
def get_hash(immutable_files: list[str]) -> str: # input should be a list
|
||||
# FIXME: should be finalized when get into the server
|
||||
current_file_path = Path(__file__).resolve()
|
||||
project_root = current_file_path.parents[2]
|
||||
file_path = f"{project_root}/tests/immutable_file/"
|
||||
file_path = "../immutable_file/" # TODO: change this when things are on the server
|
||||
immutable_hash = []
|
||||
for i, file in enumerate(immutable_files):
|
||||
immutable_files[i] = file_path + file.rsplit("/", 1)[-1]
|
||||
|
|
|
@ -14,31 +14,17 @@ class ParserResultDetail(BaseModel):
|
|||
|
||||
class ParserDummy(BaseModel):
|
||||
comment: Optional[str] = ""
|
||||
score: Optional[int] = 0
|
||||
forcequit: Optional[bool] = True
|
||||
|
||||
|
||||
class ParserKeyword(BaseModel):
|
||||
keyword: Optional[list[str]] = []
|
||||
weight: Optional[list[int]] = []
|
||||
|
||||
|
||||
class Outputs(BaseModel):
|
||||
score: Optional[int] = 0
|
||||
ignorespaces: Optional[bool] = False
|
||||
hide: Optional[bool] = False
|
||||
forcequit: Optional[bool] = True
|
||||
|
||||
|
||||
class ParserDiff(BaseModel):
|
||||
output: Optional[Outputs] = Outputs()
|
||||
keyword: Optional[list[str]] = None
|
||||
weight: Optional[list[int]] = None
|
||||
|
||||
|
||||
class Files(BaseModel):
|
||||
import_: Optional[List[str]] = Field(
|
||||
[], serialization_alias="import", validation_alias="import"
|
||||
)
|
||||
export: Optional[List[str]] = []
|
||||
import_: Optional[List[str]] = Field(serialization_alias="import", validation_alias="import")
|
||||
export: Optional[List[str]]
|
||||
|
||||
|
||||
|
||||
class Limit(BaseModel):
|
||||
|
@ -49,36 +35,20 @@ class Limit(BaseModel):
|
|||
|
||||
|
||||
class Stage(BaseModel):
|
||||
name: Optional[str] = None # Stage name
|
||||
command: Optional[str] = None # Command to run
|
||||
name: str # Stage name
|
||||
command: str # Command to run
|
||||
files: Optional[Files] = None
|
||||
score: Optional[int] = 0
|
||||
parsers: Optional[list[str]] = [] # list of parsers
|
||||
limit: Optional[Limit] = Limit()
|
||||
parsers: list[str] # list of parsers
|
||||
limit: Optional[Limit] = None
|
||||
dummy: Optional[ParserDummy] = ParserDummy()
|
||||
result_status: Optional[ParserDummy] = Field(ParserDummy(), alias="result-status")
|
||||
keyword: Optional[ParserKeyword] = ParserKeyword()
|
||||
clangtidy: Optional[ParserKeyword] = ParserKeyword()
|
||||
cppcheck: Optional[ParserKeyword] = ParserKeyword()
|
||||
# FIXME: determine cpplint type
|
||||
cpplint: Optional[ParserKeyword] = ParserKeyword()
|
||||
result_detail: Optional[ParserResultDetail] = Field(
|
||||
ParserResultDetail(), alias="result-detail"
|
||||
)
|
||||
skip: Optional[list[str]] = []
|
||||
diff: Optional[ParserDiff] = ParserDiff()
|
||||
cases: Optional[Dict[str, "Stage"]] = {}
|
||||
|
||||
class Config:
|
||||
extra = "allow"
|
||||
|
||||
@root_validator(pre=True)
|
||||
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")}
|
||||
for key in cases:
|
||||
values.pop(key)
|
||||
values["cases"] = {k: Stage(**v) for k, v in cases.items()}
|
||||
return values
|
||||
|
||||
|
||||
class Release(BaseModel):
|
||||
|
|
|
@ -6,5 +6,5 @@ sandbox_token = "test"
|
|||
[files]
|
||||
whitelist_patterns = ["*.py", "*.txt", "*.md"]
|
||||
whitelist_file = ".whitelist"
|
||||
required = [ "Changelog.md", "Readme.md" ]
|
||||
immutable = [".gitignore", ".gitattributes", ".gitea/workflows/push.yaml", ".gitea/workflows/release.yaml" ]
|
||||
required = ["main.py", "README.md"]
|
||||
immutable = [".gitignore", ".gitattributes", "push.yaml", "release.yaml"]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,31 +1,20 @@
|
|||
# p2 repo config
|
||||
|
||||
task="p2 m3" # task name
|
||||
# general task configuration
|
||||
张泊明518370910136
commented
make this basic test as simple as possible, and create new test cases for each kind of stage make this basic test as simple as possible, and create new test cases for each kind of stage
|
||||
task="Homework 1 exercise 2" # task name
|
||||
|
||||
release.deadline = 2024-10-12 23:59:00+08:00
|
||||
release.stages = [ "compile" ]
|
||||
|
||||
[files]
|
||||
immutable = [".gitignore", ".gitattributes", ".gitea/workflows/push.yaml", ".gitea/workflows/release.yaml" ]
|
||||
required = [ "Changelog.md", "Readme.md" ]
|
||||
|
||||
[[stages]]
|
||||
name = "Abuse of strings detected"
|
||||
command = "./strdetect src/"
|
||||
files.import = [ "tools/strdetec" ]
|
||||
|
||||
parsers = [ "result-status" ]
|
||||
|
||||
|
||||
[[stages]]
|
||||
name = "Compilation"
|
||||
command = "compile"
|
||||
files.import = [ "tools/compile" ]
|
||||
files.export = [ "build/onecard", "build/asan", "build/ubsan", "build/msan", "build/compile_commands.json" ]
|
||||
command = "make.sh" # eg. script running cmake commands
|
||||
files.import = [ "tools/make.sh", "src/main.c", "src/task.h", "srcCMakelist.txt" ]
|
||||
files.export = [ "driver", "p2", "p2-msan" ]
|
||||
limit.cpu = 180 # p2 takes long to compile
|
||||
limit.stderr = 128
|
||||
|
||||
# compile parsers
|
||||
parsers = [ "result-detail", "dummy", "result-status" ]
|
||||
result-status.comment = "Congratulations! Your code compiled successfully."
|
||||
result-status.score = 1
|
||||
dummy.comment = "\n\n### Details\n"
|
||||
result-detail.exitstatus = true
|
||||
result-detail.stderr = true
|
||||
|
@ -33,28 +22,26 @@ result-detail.time = false
|
|||
result-detail.mem = false
|
||||
|
||||
[[stages]]
|
||||
name = "[cq] Filelength"
|
||||
command = "./file-length 400 300 *.c *.h"
|
||||
files.import = [ "tools/filelength" ]
|
||||
name = "File length check"
|
||||
command = "./file-length 500 400 *.c *.h" # command to run
|
||||
files.import = [ "tools/file-length" ]
|
||||
|
||||
parsers = [ "keyword", "dummy", "result-detail" ]
|
||||
keyword.keyword = [ "max", "recommended"]
|
||||
keyword.weight = [ 20, 10 ]
|
||||
dummy.comment = "\n\n### Details\n"
|
||||
result-detail.exitstatus = true
|
||||
result-detail.stdout = true
|
||||
keyword.keyword = [ "max", "recommend"] # keywords caught by corresponding JOJ plugin
|
||||
keyword.weight = [ 50, 20 ] # weight of each keyword
|
||||
result-detail.exitstatus = false
|
||||
result-detail.stderr = true
|
||||
result-detail.time = false
|
||||
result-detail.mem = false
|
||||
|
||||
[[stages]]
|
||||
name = "[cq] Clang-tidy"
|
||||
name = "Clang-tidy checks"
|
||||
command = "run-clang-tidy-18 -header-filter=.* -quiet -load=/usr/local/lib/libcodequality.so -p build"
|
||||
files.import = [ "projects/p2/.clang-tidy", "build/compile_commands.json" ]
|
||||
limit.stdout = 65
|
||||
|
||||
parsers = [ "clangtidy", "dummy", "result-detail" ]
|
||||
clangtidy.keyword = [ "codequality-unchecked-malloc-result", "codequality-no-global-variables", "codequality-no-header-guard", "codequality-no-fflush-stdin", "readability-function-size", "readability-duplicate-include", "readability-identifier-naming", "readability-redundant", "readability-misleading-indentation", "readability-misplaced-array-index", "cppcoreguidelines-init-variables", "bugprone-suspicious-string-compare", "google-global-names-in-headers", "clang-diagnostic", "clang-analyzer", "misc", "performance", "portability" ]
|
||||
clangtidy.weight = [ 5, 20, 20, 20, 10, 5, 5, 5, 15, 5, 5, 5, 5, 5, 5, 5, 5, 5]
|
||||
clangtidy.keyword = [ "codequality-no-global-variables", "codequality-no-header-guard", "readability-function-size", "readability-duplicate-include", "readability-identifier-naming", "readability-redundant", "readability-misleading-indentation", "readability-misplaced-array-index", "cppcoreguidelines-init-variables", "bugprone-suspicious-string-compare", "google-global-names-in-headers", "clang-diagnostic", "clang-analyzer", "misc performance" ]
|
||||
clangtidy.weight = [10, 10, 50, 10, 5, 5, 10, 5, 5, 8, 5, 5, 5, 5, 8]
|
||||
dummy.comment = "\n\n### Details\n"
|
||||
result-detail.exitstatus = true
|
||||
result-detail.stdout = true
|
||||
|
@ -62,13 +49,13 @@ result-detail.time = false
|
|||
result-detail.mem = false
|
||||
|
||||
[[stages]]
|
||||
name = "[cq] Cppcheck"
|
||||
command = "cppcheck --template='{\"file\":\"{file}\",\"line\":{line}, \"column\":{column}, \"severity\":\"{severity}\", \"message\":\"{message}\", \"id\":\"{id}\"}' --force --enable=all --suppress=missingIncludeSystem --quiet ./"
|
||||
name = "Cppcheck check"
|
||||
command = "cppcheck --template='{\"file\":\"{file}\",\"line\":{line}, \"column\":{column}, \"severity\":\"{severity}\", \"message\":\"{message}\", \"id\":\"{id}\"}' --force --enable=all --quiet ./"
|
||||
limit.stderr = 65
|
||||
|
||||
parsers = [ "cppcheck", "dummy", "result-detail" ]
|
||||
cppcheck.keyword = ["error", "warning", "portability", "performance", "style"]
|
||||
cppcheck.weight = [15, 5, 5, 5, 5]
|
||||
cppcheck.weight = [20, 10, 15, 15, 10]
|
||||
dummy.comment = "\n\n### Details\n"
|
||||
result-detail.exitstatus = true
|
||||
result-detail.stderr = true
|
||||
|
@ -76,62 +63,57 @@ result-detail.time = false
|
|||
result-detail.mem = false
|
||||
|
||||
[[stages]]
|
||||
name = "[cq] Cpplint"
|
||||
name = "Cpplint check"
|
||||
command = "cpplint --linelength=120 --filter=-legal,-readability/casting,-whitespace,-runtime/printf,-runtime/threadsafe_fn,-readability/todo,-build/include_subdir,-build/header_guard --recursive --exclude=build ."
|
||||
limit.stdout = 65
|
||||
|
||||
parsers = [ "cpplint", "dummy", "result-detail" ]
|
||||
cpplint.keyword = [ "runtime", "readability", "build" ]
|
||||
cpplint.weight = [ 5, 20, 10]
|
||||
cpplint.weight = [ 10, 20, 15]
|
||||
dummy.comment = "\n\n### Details\n"
|
||||
result-detail.exitstatus = true
|
||||
result-detail.stderr = true
|
||||
result-detail.stdout = true
|
||||
result-detail.time = false
|
||||
result-detail.mem = false
|
||||
|
||||
[[stages]]
|
||||
name = "[run] onecard"
|
||||
group = "run"
|
||||
command="./onecard -a"
|
||||
files.import = [ "build/onecard" ]
|
||||
name = "judge-base"
|
||||
command="./driver ./mumsh"
|
||||
limit.cpu = 3
|
||||
limit.mem = 75
|
||||
score = 10
|
||||
|
||||
parsers = [ "result-status", "result-detail" ]
|
||||
result-status.score = 1
|
||||
result-status.forcequit = false
|
||||
parsers = ["diff", "dummy", "result-detail"]
|
||||
dummy.comment = "\n\n### Details\n"
|
||||
result-detail.exitstatus = true
|
||||
result-detail.stderr = true
|
||||
|
||||
case4.score = 15
|
||||
case4.limit.cpu = 30
|
||||
case4.limit.mem = 10
|
||||
case4.limit.stdout = 8
|
||||
|
||||
case5.score = 25
|
||||
|
||||
case8.limit.stderr = 128
|
||||
|
||||
[[stages]]
|
||||
name = "[run] address sanitizer"
|
||||
group = "run"
|
||||
command="./asan -a"
|
||||
files.import = [ "build/asan" ]
|
||||
name = "judge-msan"
|
||||
command="./driver ./mumsh-msan"
|
||||
limit.cpu = 10 # default cpu limit (in sec) for each test case
|
||||
limit.mem = 500 # set default mem limit (in MB) for all OJ test cases
|
||||
score = 10
|
||||
skip = ["case0", "case11"]
|
||||
|
||||
parsers = [ "result-status", "result-detail" ]
|
||||
result-status.score = 1
|
||||
result-status.forcequit = false
|
||||
parsers = ["diff", "dummy", "result-detail"]
|
||||
dummy.comment = "\n\n### Details\n"
|
||||
result-detail.exitstatus = true
|
||||
result-detail.stderr = true
|
||||
|
||||
[[stages]]
|
||||
name = "[run] memory sanitizer"
|
||||
group = "run"
|
||||
command="./msan -a"
|
||||
files.import = [ "build/msan" ]
|
||||
case4.score = 15
|
||||
case4.limit.cpu = 30
|
||||
case4.limit.mem = 10
|
||||
|
||||
parsers = [ "result-status", "result-detail" ]
|
||||
result-status.score = 1
|
||||
result-status.forcequit = false
|
||||
result-detail.exitstatus = true
|
||||
result-detail.stderr = true
|
||||
case5.diff.output.ignorespaces = false
|
||||
|
||||
[[stages]]
|
||||
name = "[run] undefined behavior sanitizer"
|
||||
command="./ubsan -a"
|
||||
files.import = [ "build/ubsan" ]
|
||||
|
||||
parsers = [ "result-status", "result-detail" ]
|
||||
result-status.score = 1
|
||||
result-status.forcequit = false
|
||||
result-detail.exitstatus = true
|
||||
result-detail.stderr = true
|
||||
case6.diff.output.hide = true
|
||||
|
|
Loading…
Reference in New Issue
Block a user
hard coded path not acceptable
thats what I wanted to discuss, in what form would be better? @manuel @bomingzh
my suggestions is to add more fields related to this in
repo.toml
ortask.toml
, I think store it globally in some*.py
file is still hardcodedjust let them enter path relative to project root for immutable files
giving prompt? Wouldn't it be better to export field in
*.toml
file? they may accidnetally input wrong things I guessjust throw error on wrong input
I mean, they input
\home\tt\.config\
instead of\home\tt\.config\joj
such things may happen, but will cost time to figure out what happened? So I think better put them in*.toml
.just make the dir relative to git repo root, if the file does not exist, throw error
any path in toml should probably be defined relative to
/home/tt/.config/joj
and be in a subdirectory. this is where all config must be.then they can specify
projects/p2/.cang-tidy
ortool/compile
using
project_root
as too tricky as it could mean going up (../../
) which is hard to read/figure out quickly.as all JOJ config files must be in
$HOME/.config/joj
taking it as root makes sense, is general, and consistent with the approach we have used so farbit mixed here, if fixed, can I hardcode it?