Coverage for src / lexigram / contracts / cli / parsers.py: 31%

54 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-19 05:41 +0800

1"""Field type specification parser for CLI code generators.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6import re 

7 

8_FIELD_PATTERN = re.compile( 

9 r"^(?P<name>[a-zA-Z_][a-zA-Z0-9_]*):(?P<type>[a-zA-Z_][a-zA-Z0-9_]*)(?P<rest>.*)$", 

10) 

11_FIELD_SPLIT_PATTERN = re.compile(r",(?=\s*[a-zA-Z_][a-zA-Z0-9_]*:)") 

12 

13 

14@dataclass(slots=True, frozen=True) 

15class FieldSpec: 

16 """Specification for a single field parsed from a CLI field string.""" 

17 

18 name: str 

19 type: str 

20 required: bool = True 

21 unique: bool = False 

22 fk: str | None = None 

23 default: str | None = None 

24 

25 

26def parse_fields(fields_str: str) -> list[FieldSpec]: 

27 """Parse a comma-separated field spec string. 

28 

29 Format: ``name:type[?][!unique][!fk=Model][=default]`` 

30 """ 

31 if not fields_str.strip(): 

32 return [] 

33 fields: list[FieldSpec] = [] 

34 for raw in _FIELD_SPLIT_PATTERN.split(fields_str): 

35 raw = raw.strip() 

36 if not raw: 

37 raise ValueError("Invalid field specification: empty segment") 

38 fields.append(_parse_field(raw)) 

39 return fields 

40 

41 

42def _parse_field(field_text: str) -> FieldSpec: 

43 m = _FIELD_PATTERN.match(field_text) 

44 if m is None: 

45 raise ValueError(f"Invalid field specification: {field_text!r}") 

46 name, type_, rest = m.group("name"), m.group("type"), m.group("rest") 

47 required, unique, fk, default = True, False, None, None 

48 if rest.startswith("?"): 

49 required = False 

50 rest = rest[1:] 

51 while rest: 

52 if rest.startswith("!unique"): 

53 unique = True 

54 rest = rest[len("!unique") :] 

55 elif rest.startswith("!fk="): 

56 fk, rest = _parse_foreign_key(rest, field_text) 

57 elif rest.startswith("="): 

58 default = rest[1:] 

59 rest = "" 

60 else: 

61 raise ValueError(f"Invalid field specification: {field_text!r}") 

62 return FieldSpec( 

63 name=name, type=type_, required=required, unique=unique, fk=fk, default=default 

64 ) 

65 

66 

67def _parse_foreign_key(remainder: str, field_text: str) -> tuple[str, str]: 

68 value = remainder[len("!fk=") :] 

69 idxs = [i for i in (value.find("!"), value.find("=")) if i != -1] 

70 split = min(idxs, default=-1) 

71 if split == -1: 

72 fk, rest = value, "" 

73 else: 

74 fk, rest = value[:split], value[split:] 

75 if not fk: 

76 raise ValueError(f"Invalid field specification: {field_text!r}") 

77 return fk, rest 

78 

79 

80__all__ = ["FieldSpec", "parse_fields"]