Загрузка данных
#!/usr/bin/env python3
"""Проверка синтаксической корректности строки вызова утилиты sort (sort_23).
Ввод: строки со стандартного ввода. Вывод:
sort ... -> True / False
пустая строка -> False
getstatistic -> "<успешных> <неуспешных> <всего>"
getsuccesscount -> число успешных проверок
getfailedcount -> число неуспешных проверок
resetstatistic -> (нет вывода) сброс статистики
Статистика хранится во временном файле (используется только для неё).
"""
import os
import re
import shlex
import sys
# Путь к файлу статистики
STATS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "stats")
# ----------------------------- статистика ---------------------------------
class Statistics:
def __init__(self, success_count=0, fail_count=0):
self.success_count = success_count
self.fail_count = fail_count
@property
def total_count(self):
return self.success_count + self.fail_count
def read_stats():
if not os.path.exists(STATS_FILE):
return Statistics()
try:
with open(STATS_FILE, "r", encoding="utf-8") as f:
line = f.read().strip()
if line:
succ, fail, _ = map(int, line.split())
return Statistics(success_count=succ, fail_count=fail)
except Exception:
pass
return Statistics()
def write_stats(stats):
try:
os.makedirs(os.path.dirname(STATS_FILE), exist_ok=True)
with open(STATS_FILE, "w", encoding="utf-8") as f:
f.write(f"{stats.success_count} {stats.fail_count} {stats.total_count}\n")
except Exception:
pass
# ----------------------- описание ключей из ТЗ ----------------------------
# Короткие ключи без аргумента
SHORT_NOARG = set("bdfgiMhnRrVcCmsuz")
# Короткие ключи с обязательным аргументом: -k KEYDEF, -o FILE, -S SIZE,
# -t SEP, -T DIR
SHORT_ARG = set("koSTt")
# Длинные ключи без аргумента -> внутренний идентификатор
LONG_NOARG = {
"ignore-leading-blanks": "b",
"dictionary-order": "d",
"ignore-case": "f",
"general-numeric-sort": "g",
"ignore-nonprinting": "i",
"month-sort": "M",
"human-numeric-sort": "h",
"numeric-sort": "n",
"random-sort": "R",
"reverse": "r",
"version-sort": "V",
"merge": "m",
"stable": "s",
"unique": "u",
"zero-terminated": "z",
"debug": "debug",
"help": "help",
"version": "version",
}
# Длинные ключи с обязательным аргументом (--name=VALUE или --name VALUE)
LONG_ARG = {
"random-source": "random-source",
"sort": "sort",
"batch-size": "batch-size",
"compress-program": "compress-program",
"files0-from": "files0-from",
"key": "k",
"output": "o",
"buffer-size": "S",
"field-separator": "t",
"temporary-directory": "T",
"parallel": "parallel",
}
# --check[=diagnose-first|quiet|silent]: -c / -C
CHECK_VALUES = {"diagnose-first": "c", "quiet": "C", "silent": "C"}
# --sort=WORD -> эквивалентный короткий ключ
SORT_WORDS = {
"general-numeric": "g", "human-numeric": "h", "month": "M",
"numeric": "n", "random": "R", "version": "V",
"g": "g", "h": "h", "M": "M", "n": "n", "R": "R", "V": "V",
}
# Таблица несовместимых ключей из ТЗ (строка -> несовместимые с ней)
CONFLICT_ROWS = {
"n": ["g", "h", "V", "M", "R"],
"g": ["n", "h", "V", "M", "R"],
"h": ["n", "g", "V", "M", "R"],
"V": ["n", "g", "h", "M", "R"],
"M": ["n", "g", "h", "V", "R"],
"R": ["n", "g", "h", "V", "M", "c", "C"],
"c": ["u", "o", "m", "R", "s", "S", "T", "batch-size",
"compress-program", "debug", "parallel"],
"C": ["o", "m", "R", "S", "T", "batch-size", "compress-program", "parallel"],
"m": ["u", "c", "C"],
"s": ["u", "c"],
"u": ["c", "m", "s"],
"o": ["c", "C"],
"b": ["d", "i"],
"d": ["b", "i", "f"],
"f": ["d", "i"],
"i": ["b", "d", "f"],
"z": ["files0-from"],
}
# Несовместимость симметрична: превращаем таблицу в множество пар
CONFLICT_PAIRS = set()
for _a, _row in CONFLICT_ROWS.items():
for _b in _row:
CONFLICT_PAIRS.add(frozenset((_a, _b)))
# KEYDEF: F[.C][OPTS][,F[.C][OPTS]]; поле >= 1, начальный символ >= 1,
# конечный символ >= 0 (0 = до конца поля); OPTS - модификаторы упорядочения
KEYDEF_RE = re.compile(
r"^[1-9]\d*(\.[1-9]\d*)?[bdfghiMnRrV]*(,[1-9]\d*(\.\d+)?[bdfghiMnRrV]*)?$"
)
SIZE_RE = re.compile(r"^(\d+)([%bkKMGTPEZYRQ]?)$")
# --------------------------- проверка значений ----------------------------
def valid_keydef(v):
return KEYDEF_RE.match(v) is not None
def valid_separator(v):
# SEP - одиночный символ (допускается запись \0)
return len(v) == 1 or v == "\\0"
def valid_size(v):
# SIZE - число (допускается суффикс) или проценты
m = SIZE_RE.match(v)
if not m:
return False
if m.group(2) == "%":
return 0 <= int(m.group(1)) <= 100
return True
def valid_nonempty(v):
return v != ""
def valid_number(v):
return re.match(r"^\d+$", v) is not None
def valid_positive(v):
return re.match(r"^[1-9]\d*$", v) is not None
def valid_sort_word(v):
return v in SORT_WORDS
VALIDATORS = {
"k": valid_keydef,
"t": valid_separator,
"S": valid_size,
"T": valid_nonempty,
"o": valid_nonempty,
"random-source": valid_nonempty,
"compress-program": valid_nonempty,
"files0-from": valid_nonempty,
"batch-size": valid_number,
"parallel": valid_positive,
"sort": valid_sort_word,
}
# ------------------------------ разбор строки -----------------------------
def parse_sort_command(command):
"""Возвращает (имя команды, ключи, файлы, успех разбора).
ключи - список пар (идентификатор, значение или None).
"""
try:
tokens = shlex.split(command) # учитывает кавычки: -t ':' , -t ''
except ValueError: # незакрытая кавычка
return "", [], [], False
if not tokens:
return "", [], [], False
cmd_name = tokens[0]
keys = []
file_args = []
end_of_options = False
i = 1
while i < len(tokens):
token = tokens[i]
if end_of_options or token == "-" or not token.startswith("-"):
file_args.append(token)
elif token == "--":
end_of_options = True
elif token.startswith("--") or token.startswith("-compress-program="):
name, has_eq, value = token.lstrip("-").partition("=")
if name == "check":
if not has_eq:
keys.append(("c", None))
elif value in CHECK_VALUES:
keys.append((CHECK_VALUES[value], None))
else:
return cmd_name, keys, file_args, False
elif name in LONG_NOARG:
if has_eq:
return cmd_name, keys, file_args, False
keys.append((LONG_NOARG[name], None))
elif name in LONG_ARG:
if not has_eq:
i += 1
if i >= len(tokens):
return cmd_name, keys, file_args, False
value = tokens[i]
keys.append((LONG_ARG[name], value))
else:
return cmd_name, keys, file_args, False
else: # короткие ключи, возможно слитно: -nr, -k2,2, -t:
j = 1
while j < len(token):
ch = token[j]
if ch in SHORT_NOARG:
keys.append((ch, None))
j += 1
elif ch in SHORT_ARG:
value = token[j + 1:]
if value == "":
i += 1
if i >= len(tokens):
return cmd_name, keys, file_args, False
value = tokens[i]
keys.append((ch, value))
break
else:
return cmd_name, keys, file_args, False
i += 1
return cmd_name, keys, file_args, True
# ------------------------------ проверки ----------------------------------
def check_command_name(cmd_name):
return cmd_name == "sort"
def check_keys(keys):
# формат аргументов ключей
for key, value in keys:
validator = VALIDATORS.get(key)
if validator is not None and not validator(value):
return False
# конфликты ключей
active = set()
for key, value in keys:
active.add(SORT_WORDS[value] if key == "sort" else key)
for pair in CONFLICT_PAIRS:
if pair <= active:
return False
return True
def check_file_arguments(file_args):
# отсутствие файлов допускается (чтение из stdin)
return True
def check_command_logic(command):
cmd_name, keys, file_args, ok = parse_sort_command(command)
if not ok:
return False
return (check_command_name(cmd_name)
and check_keys(keys)
and check_file_arguments(file_args))
# ----------------------------- ввод / вывод -------------------------------
def command_input(line):
stripped = line.strip()
if stripped == "resetstatistic":
write_stats(Statistics())
return ""
elif stripped == "getsuccesscount":
return str(read_stats().success_count)
elif stripped == "getfailedcount":
return str(read_stats().fail_count)
elif stripped == "getstatistic":
st = read_stats()
return f"{st.success_count} {st.fail_count} {st.total_count}"
# пустая строка - ошибочная проверка (False)
is_valid = bool(stripped) and check_command_logic(stripped)
stats = read_stats()
if is_valid:
stats.success_count += 1
else:
stats.fail_count += 1
write_stats(stats)
return "True" if is_valid else "False"
def main():
if sys.stdin.isatty():
while True:
try:
user_input = sys.stdin.readline()
if not user_input:
break
cleaned_input = user_input.strip()
if cleaned_input.lower() == "exit":
break
output = command_input(cleaned_input)
if output:
print(output)
except (KeyboardInterrupt, EOFError):
break
else:
# Читаем все строки сразу, чтобы корректно обработать полностью пустой stdin
lines = sys.stdin.readlines()
if not lines:
# пустой ввод - ошибочная проверка
output = command_input("")
if output:
print(output)
else:
for raw_line in lines:
output = command_input(raw_line.strip())
if output:
print(output)
if __name__ == "__main__":
main()