2
2
3
3
import argparse
4
4
import itertools
5
- import json
6
5
import os
7
6
import subprocess
8
7
import time
14
13
from pathlib import Path
15
14
from zlib import crc32
16
15
import sqlite3
16
+ import shutil
17
17
18
18
RED = "\033 [91m"
19
19
GREEN = "\033 [92m"
33
33
34
34
LANGUAGES = {
35
35
"Python" : "{year}/day{day}/day{day}.py" ,
36
- "PyPy" : "{year}/day{day}/day{day}.py" ,
37
36
"Rust" : "{year}/target/release/day{day}" ,
38
37
"C" : "{year}/build/day{day}_c" ,
39
38
"C++" : "{year}/build/day{day}_cpp" ,
40
39
}
41
40
42
- DEFAULT_LANGUAGES = {"Python" , "Rust" }
43
-
44
41
INTERPRETERS = {
45
- "Python" : "python3" ,
46
- "PyPy" : "pypy3" ,
42
+ "Python" : {
43
+ "Python" : "python3" ,
44
+ "PyPy" : ".pypy3.10/bin/python" ,
45
+ "Py3.10" : ".py3.10/bin/python" ,
46
+ "Py3.11" : ".py3.11/bin/python" ,
47
+ "Py3.12" : ".py3.12/bin/python" ,
48
+ "Py3.13" : ".py3.13/bin/python" ,
49
+ }
47
50
}
48
51
49
52
@@ -61,7 +64,13 @@ def get_cache():
61
64
" mtime_ns int,"
62
65
" elapsed float,"
63
66
" status text,"
64
- " answers text);"
67
+ " answers text"
68
+ ");"
69
+ "create table if not exists inputs ("
70
+ " key text primary key not null,"
71
+ " mtime_ns int,"
72
+ " crc32 text"
73
+ ");"
65
74
)
66
75
cache = {"db" : cache_db , "modified" : False }
67
76
globals ()["_cache" ] = cache
@@ -78,20 +87,18 @@ def save_cache():
78
87
# print(f"{FEINT}{ITALIC}cache commited{RESET}")
79
88
80
89
81
- def check_cache (key , file_timestamp : Path , no_age_check = False ):
90
+ def check_cache (key , file_timestamp : Path , table : str , columns : t . Iterable [ str ], no_age_check = False ):
82
91
cache = get_cache ()
83
92
key = str (key )
84
93
db = cache ["db" ]
85
- cursor = db .execute ("select mtime_ns,elapsed,status,answers from solutions where key=?" , (key ,))
94
+ db .row_factory = sqlite3 .Row
95
+ cursor = db .execute (f"select * from `{ table } ` where key=?" , (key ,))
86
96
row = cursor .fetchone ()
87
97
if row :
88
98
timestamp = file_timestamp .stat ().st_mtime_ns
89
- if row [0 ] == timestamp or no_age_check :
90
- return {
91
- "elapsed" : row [1 ],
92
- "status" : row [2 ],
93
- "answers" : row [3 ].split ("\n " ),
94
- }
99
+ if row ["mtime_ns" ] == timestamp or no_age_check :
100
+ return dict ((column , row [column ]) for column in columns )
101
+
95
102
else :
96
103
# seconds = round((timestamp - e["timestamp"]) / 1000000000)
97
104
# delta = timedelta(seconds=seconds)
@@ -103,34 +110,40 @@ def check_cache(key, file_timestamp: Path, no_age_check=False):
103
110
print (f"{ FEINT } { ITALIC } missing cache for { key } { RESET } " , end = f"{ CLEAR_EOL } { CR } " )
104
111
105
112
106
- def update_cache (key , timestamp : Path , elapsed : float , status : str , answers : t . Iterable ) :
113
+ def update_cache (key , timestamp : Path , table : str , row : t . Dict [ str , t . Union [ str , int ]]) -> None :
107
114
cache = get_cache ()
108
115
db = cache ["db" ]
109
- key = str (key )
110
116
111
- db .execute (
112
- "insert or replace into solutions (key,mtime_ns,elapsed,status,answers) values (?,?,?,?,?)" ,
113
- (key , timestamp .stat ().st_mtime_ns , elapsed , status , "\n " .join (answers )),
114
- )
117
+ sql = f"insert or replace into `{ table } ` (key,mtime_ns"
118
+ values = [str (key ), timestamp .stat ().st_mtime_ns ]
115
119
116
- # cache["modified"] = True
117
- db .commit ()
120
+ for k , v in row .items ():
121
+ sql += f",`{ k } `"
122
+ values .append (v )
118
123
119
- return {
120
- "elapsed" : elapsed ,
121
- "status" : status ,
122
- "answers" : answers ,
123
- }
124
+ sql += ") values (?,?"
125
+ sql += ",?" * len (row )
126
+ sql += ")"
127
+
128
+ db .execute (sql , values )
129
+
130
+ db .commit ()
124
131
125
132
126
- def run (prog : Path , lang : str , file : Path , solution : t .List , warmup : bool ) -> t .Dict [str , t .Any ]:
133
+ def run (
134
+ prog : Path ,
135
+ lang : str ,
136
+ interpreter : t .Union [None , str ],
137
+ file : Path ,
138
+ solution : t .List ,
139
+ warmup : bool ,
140
+ ) -> t .Dict [str , t .Any ]:
127
141
if not prog .is_file ():
128
142
return
129
143
130
144
cmd = [prog .absolute ().as_posix ()]
131
145
132
146
# add the interpreter
133
- interpreter = INTERPRETERS .get (lang )
134
147
if interpreter :
135
148
cmd .insert (0 , interpreter )
136
149
@@ -156,7 +169,7 @@ def run(prog: Path, lang: str, file: Path, solution: t.List, warmup: bool) -> t.
156
169
else :
157
170
status = "unknown"
158
171
159
- return {"elapsed" : elapsed , "status" : status , "answers" : answers . split ( " \n " ) }
172
+ return {"elapsed" : elapsed , "status" : status , "answers" : answers }
160
173
161
174
162
175
def make (year : Path , source : Path , dest : Path , cmd : str ):
@@ -211,7 +224,9 @@ def load_data(filter_year, filter_user):
211
224
212
225
assert len (f .parts ) == 4
213
226
214
- if filter_user and f .parent .parent .name != filter_user :
227
+ user = f .parent .parent .name
228
+
229
+ if filter_user and user != filter_user :
215
230
continue
216
231
217
232
year = int (f .parent .name )
@@ -220,12 +235,14 @@ def load_data(filter_year, filter_user):
220
235
if filter_year != 0 and year != filter_year :
221
236
continue
222
237
223
- e = check_cache (f , f )
238
+ key = f"{ year } :{ day } :{ user } "
239
+
240
+ e = check_cache (key , f , "inputs" , ("crc32" ,))
224
241
if e :
225
- crc = e ["status " ]
242
+ crc = e ["crc32 " ]
226
243
else :
227
244
crc = hex (crc32 (f .read_bytes ().strip ()) & 0xFFFFFFFF )
228
- update_cache (f , f , 0 , crc , [] )
245
+ update_cache (key , f , "inputs" , { "crc32" : crc } )
229
246
230
247
if crc not in inputs [year , day ]:
231
248
inputs [year , day ][crc ] = f
@@ -239,7 +256,15 @@ def load_data(filter_year, filter_user):
239
256
240
257
241
258
def run_day (
242
- year : int , day : int , mday : str , day_inputs : t .Dict , day_sols : t .Dict , problems : t .Set , filter_lang , refresh , dry_run
259
+ year : int ,
260
+ day : int ,
261
+ mday : str ,
262
+ day_inputs : t .Dict ,
263
+ day_sols : t .Dict ,
264
+ languages : dict ,
265
+ problems : t .Set ,
266
+ refresh : bool ,
267
+ dry_run : bool ,
243
268
):
244
269
elapsed = defaultdict (list )
245
270
@@ -259,14 +284,7 @@ def run_day(
259
284
260
285
results = set ()
261
286
262
- for lang , pattern in LANGUAGES .items ():
263
- if filter_lang == "all" :
264
- pass
265
- elif filter_lang :
266
- if lang .lower () != filter_lang .lower ():
267
- continue
268
- elif lang not in DEFAULT_LANGUAGES :
269
- continue
287
+ for lang , (pattern , interpreter ) in languages .items ():
270
288
271
289
prog = Path (pattern .format (year = year , day = mday ))
272
290
key = ":" .join (map (str , (year , day , crc , prog , lang .lower ())))
@@ -275,16 +293,16 @@ def run_day(
275
293
e = None
276
294
in_cache = False
277
295
else :
278
- e = check_cache (key , prog , dry_run )
296
+ e = check_cache (key , prog , "solutions" , ( "elapsed" , "status" , "answers" ), dry_run )
279
297
in_cache = e is not None
280
298
281
299
if not in_cache and not dry_run :
282
300
283
- e = run (prog , lang , file , day_sols .get (crc ), warmup [lang ])
301
+ e = run (prog , lang , interpreter , file , day_sols .get (crc ), warmup [lang ])
284
302
285
303
if e :
286
304
warmup [lang ] = False
287
- e = update_cache (key , prog , e [ "elapsed" ] , e [ "status" ], e [ "answers" ] )
305
+ update_cache (key , prog , "solutions" , e )
288
306
289
307
if not e :
290
308
continue
@@ -296,14 +314,16 @@ def run_day(
296
314
297
315
status_color = {"missing" : MAGENTA , "unknown" : GRAY , "error" : RED , "ok" : GREEN }[e ["status" ]]
298
316
317
+ answers = e ["answers" ].split ("\n " )
318
+
299
319
line = (
300
320
f"{ CR } { RESET } { CLEAR_EOL } "
301
321
f"{ prefix } "
302
322
f" { YELLOW } { lang :<7} { RESET } :"
303
323
f" { status_color } { e ['status' ]:7} { RESET } "
304
324
f" { WHITE } { e ['elapsed' ]/ 1e9 :7.3f} s"
305
325
f" { GRAY } { '☽' if in_cache else ' ' } "
306
- f" { status_color } { str (e [ ' answers' ] ):<40} { RESET } "
326
+ f" { status_color } { str (answers ):<40} { RESET } "
307
327
f"{ info } "
308
328
)
309
329
print (line )
@@ -314,7 +334,7 @@ def run_day(
314
334
if not in_cache and e ["elapsed" ] / 1e9 > 5 :
315
335
save_cache ()
316
336
317
- results .add (" " .join (e [ " answers" ] ))
337
+ results .add (" " .join (answers ))
318
338
319
339
elapsed [lang ].append (e ["elapsed" ] / 1e9 )
320
340
@@ -330,9 +350,83 @@ def run_day(
330
350
return dict ((lang , sum (t ) / len (t )) for lang , t in elapsed .items ()), nb_samples
331
351
332
352
353
+ def get_languages (filter_lang : t .Iterable [str ]) -> t .Dict [str , t .Tuple [str , t .Union [str , None ]]]:
354
+
355
+ filter_lang = set (map (str .casefold , filter_lang or ()))
356
+
357
+ languages = {}
358
+ for lang , v in LANGUAGES .items ():
359
+
360
+ if lang in INTERPRETERS :
361
+ for lang2 , interpreter in INTERPRETERS [lang ].items ():
362
+
363
+ if filter_lang and lang2 .casefold () not in filter_lang :
364
+ continue
365
+
366
+ if "/" not in interpreter and "\\ " not in interpreter :
367
+ interpreter = shutil .which (interpreter )
368
+ languages [lang2 ] = (v , interpreter )
369
+ else :
370
+ interpreter = Path (interpreter ).expanduser ().absolute ()
371
+ if interpreter .is_file () and (interpreter .stat ().st_mode & os .X_OK ) != 0 :
372
+ languages [lang2 ] = (v , interpreter .as_posix ())
373
+ else :
374
+ # print(f"language {lang2} : interpreter {interpreter} not found")
375
+ pass
376
+ else :
377
+
378
+ if filter_lang and lang .casefold () not in filter_lang :
379
+ continue
380
+ languages [lang ] = (v , None )
381
+
382
+ return languages
383
+
384
+
385
+ def install_venv (interpreter : Path ):
386
+
387
+ try :
388
+ slug = 'import sys;print(((hasattr(sys, "subversion") and getattr(sys, "subversion")) or ("Py",))[0] + f"{sys.version_info.major}.{sys.version_info.minor}")'
389
+
390
+ slug = subprocess .check_output ([interpreter , "-c" , slug ]).decode ().strip ()
391
+
392
+ venv = "." + slug .lower ()
393
+
394
+ # subprocess.check_call([interpreter, "-mensurepip"])
395
+ subprocess .check_output ([interpreter , "-mvenv" , venv ])
396
+ subprocess .check_call (
397
+ [
398
+ f"{ venv } /bin/python3" ,
399
+ "-mpip" ,
400
+ "install" ,
401
+ "--no-input" ,
402
+ "--quiet" ,
403
+ "--upgrade" ,
404
+ "pip" ,
405
+ ]
406
+ )
407
+ subprocess .check_call (
408
+ [
409
+ f"{ venv } /bin/python3" ,
410
+ "-mpip" ,
411
+ "install" ,
412
+ "--no-input" ,
413
+ "--quiet" ,
414
+ "--upgrade" ,
415
+ "-r" ,
416
+ "scripts/requirements.txt" ,
417
+ ]
418
+ )
419
+
420
+ print (f"Virtual environment for { MAGENTA } { interpreter } { RESET } installed into: { GREEN } { venv } { RESET } " )
421
+
422
+ except subprocess .CalledProcessError as e :
423
+ print (e )
424
+
425
+
333
426
def main ():
334
427
parser = argparse .ArgumentParser ()
335
- parser .add_argument ("-l" , "--language" , type = str , metavar = "LANG" , help = "filter by language" )
428
+ parser .add_argument ("--venv" , type = Path , help = "install virtual environment" )
429
+ parser .add_argument ("-l" , "--language" , type = str , action = "append" , metavar = "LANG" , help = "filter by language" )
336
430
parser .add_argument ("-r" , "--refresh" , action = "store_true" , help = "relaunch solutions" )
337
431
parser .add_argument ("-n" , "--dry-run" , action = "store_true" , help = "do not run" )
338
432
parser .add_argument ("--no-build" , action = "store_true" , help = "do not build" )
@@ -341,21 +435,24 @@ def main():
341
435
342
436
args = parser .parse_args ()
343
437
344
- filter_year = 0 if len (args .n ) == 0 else int (args .n .pop (0 ))
345
- filter_day = set (args .n )
346
- if args .language == "cpp" :
347
- args .language = "c++"
348
-
349
438
try :
350
- os .chdir (Path (__file__ ).parent .parent )
351
-
352
439
problems = []
353
440
stats_elapsed = dict ()
354
441
442
+ os .chdir (Path (__file__ ).parent .parent )
443
+
444
+ if args .venv :
445
+ return install_venv (args .venv )
446
+
447
+ filter_year = 0 if len (args .n ) == 0 else int (args .n .pop (0 ))
448
+ filter_day = set (args .n )
449
+
355
450
if not args .no_build :
356
451
build_all (filter_year )
357
452
print (end = f"{ CR } { CLEAR_EOL } " )
358
453
454
+ languages = get_languages (args .language )
455
+
359
456
inputs , sols = load_data (filter_year , args .filter_user )
360
457
361
458
for year in range (2015 , 2024 ):
@@ -375,8 +472,8 @@ def main():
375
472
mday ,
376
473
inputs [year , day ],
377
474
sols [year , day ],
475
+ languages ,
378
476
problems ,
379
- args .language ,
380
477
args .refresh ,
381
478
args .dry_run ,
382
479
)
0 commit comments