Skip to content

Commit

Permalink
sistana: basically acceptable
Browse files Browse the repository at this point in the history
  • Loading branch information
GreyElaina committed Oct 9, 2024
1 parent 2247411 commit a25ca9d
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 10 deletions.
3 changes: 2 additions & 1 deletion src/arclet/alconna/sistana/err.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import re
from dataclasses import dataclass
from typing import Any


class ReasonableParseError(Exception): ...
Expand All @@ -22,7 +23,7 @@ class ValidateRejected(Rejected): ...
@dataclass
class UnexpectedType(CaptureRejected):
expected: type | tuple[type, ...]
got: type
got: type | Any

def __str__(self):
return f"Expected {self.expected}, got {self.got}"
Expand Down
12 changes: 11 additions & 1 deletion src/arclet/alconna/sistana/fragment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Literal
from dataclasses import dataclass
from elaina_segment import Segment, Quoted, UnmatchedQuoted

Expand All @@ -17,6 +17,16 @@
class Fragment(_Fragment):
type: Some[Any] = None
cast: bool = True
prefer_checker: Literal["msgspec", "nepattern"] = "nepattern"

def __post_init__(self):
if self.type is not None:
if self.prefer_checker == "msgspec":
self.apply_msgspec()
elif self.prefer_checker == "nepattern":
self.apply_nepattern()
else:
raise ValueError("Invalid prefer_checker value.")

def apply_msgspec(self):
if self.type is None:
Expand Down
23 changes: 16 additions & 7 deletions src/arclet/alconna/sistana/model/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,14 @@ def capture(self, buffer: Buffer[Any], separators: str) -> CaptureResult[Plain]:
if isinstance(token.val, str):
return token.val, None, token
elif isinstance(token.val, (Quoted, UnmatchedQuoted)):
if not isinstance(token.val.ref, str):
raise UnexpectedType(str, type(token.val.ref))

return token.val, None, token
if isinstance(token.val.ref, str):
val = token.val.ref
elif next((i for i in token.val.ref if not isinstance(i, str)), None) is None:
val = "".join(token.val.ref)
else:
raise UnexpectedType(str, type(next(i for i in token.val.ref if not isinstance(i, str))))

return val, None, token
else:
raise UnexpectedType(str, type(token.val))

Expand All @@ -70,10 +74,15 @@ def capture(self, buffer: Buffer[Any], separators: str) -> CaptureResult[re.Matc

if isinstance(token.val, str):
val = token.val
elif isinstance(token.val, (Quoted, UnmatchedQuoted)) and isinstance(token.val.ref, str) and self.match_quote:
val = str(token.val)
elif isinstance(token.val, (Quoted, UnmatchedQuoted)) and self.match_quote:
if isinstance(token.val.ref, str):
val = token.val.ref
elif next((i for i in token.val.ref if not isinstance(i, str)), None) is None:
val = "".join(token.val.ref)
else:
raise UnexpectedType(str, type(next(i for i in token.val.ref if not isinstance(i, str))))
else:
raise UnexpectedType(str, type(token.val))
raise UnexpectedType(str, token.val)

match = re.match(self.pattern, val)
if not match:
Expand Down
1 change: 1 addition & 0 deletions src/arclet/alconna/sistana/model/mix.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ def __init__(self, subcommand_track: Track, option_tracks: dict[str, Track]):
self.option_tracks = option_tracks

assert_fragments_order(subcommand_track.fragments)

for track in self.option_tracks.values():
assert_fragments_order(track.fragments)

Expand Down
113 changes: 113 additions & 0 deletions tests/sistana/test_capture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from __future__ import annotations

import re

import pytest
from elaina_segment import Buffer

from arclet.alconna.sistana import Fragment, SubcommandPattern
from arclet.alconna.sistana.analyzer import LoopflowExitReason
from arclet.alconna.sistana.err import RegexMismatch, UnexpectedType
from arclet.alconna.sistana.model.capture import ObjectCapture, PlainCapture, RegexCapture
from arclet.alconna.sistana.some import Value

from .asserts import analyze


def test_object_capture():
pat = SubcommandPattern.build("test")
pat.option("--name", Fragment("name", capture=ObjectCapture(int)))

a, sn, bf = analyze(pat, Buffer(["test --name", 1]))
a.expect_completed()
sn.expect_determined()

frag = sn.mix[("test",), "--name"]["name"]
frag.expect_assigned()
frag.expect_value(1)

with pytest.raises(UnexpectedType, match=f"Expected {int}, got {str}"):
a, sn, bf = analyze(pat, Buffer(["test --name 1"]))


def test_plain_capture():
pat = SubcommandPattern.build("test")
pat.option("--name", Fragment("name", capture=PlainCapture()))

a, sn, bf = analyze(pat, Buffer(["test --name hello"]))
a.expect_completed()
sn.expect_determined()

frag = sn.mix[("test",), "--name"]["name"]
frag.expect_assigned()
frag.expect_value("hello")

with pytest.raises(UnexpectedType, match=f"Expected {str}, got {int}"):
# Quoted
a, sn, bf = analyze(pat, Buffer(["test --name '", 1, "'"]))

with pytest.raises(UnexpectedType, match=f"Expected {str}, got {int}"):
# UnmatchedQuoted
a, sn, bf = analyze(pat, Buffer(["test --name '", 1]))

with pytest.raises(UnexpectedType, match=f"Expected {str}, got {int}"):
a, sn, bf = analyze(pat, Buffer(["test --name", 1]))

with pytest.raises(UnexpectedType, match=f"Expected {str}, got {int}"):
# raise for first segment that is not string
a, sn, bf = analyze(pat, Buffer(["test --name '123", 2]))


def test_regex_capture():
pat = SubcommandPattern.build("test")
pat.option("--name", Fragment("name", capture=RegexCapture(r"\d+")))

a, sn, bf = analyze(pat, Buffer(["test --name 123"]))
a.expect_completed()
sn.expect_determined()

frag = sn.mix[("test",), "--name"]["name"]
frag.expect_assigned()
assert isinstance(frag.value, re.Match)
assert frag.value.group() == "123"

with pytest.raises(UnexpectedType, match=f"Expected {str}, got 1"):
a, sn, bf = analyze(pat, Buffer(["test --name", 1]))

with pytest.raises(RegexMismatch):
a, sn, bf = analyze(pat, Buffer(["test --name abc"]))

with pytest.raises(UnexpectedType, match=f"Expected {str}, got .*"):
# reason: match_quote = False
a, sn, bf = analyze(pat, Buffer(["test --name '123", 2]))

# tail
pat = SubcommandPattern.build("test")
pat.option("--name", Fragment("name", capture=RegexCapture(r"123")), Fragment("tail"))

a, sn, bf = analyze(pat, Buffer(["test --name 123345"]))
a.expect_completed()

frag = sn.mix[("test",), "--name"]["name"]
frag.expect_assigned()
assert isinstance(frag.value, re.Match)
assert frag.value.group() == "123"

frag = sn.mix[("test",), "--name"]["tail"]
frag.expect_assigned()
frag.expect_value("345")

# match_quote
pat = SubcommandPattern.build("test")
pat.option("--name", Fragment("name", capture=RegexCapture(r"123", match_quote=True)))

a, sn, bf = analyze(pat, Buffer(["test --name '123'"]))
a.expect_completed()

frag = sn.mix[("test",), "--name"]["name"]
frag.expect_assigned()
assert isinstance(frag.value, re.Match)
assert frag.value.group() == "123"

with pytest.raises(UnexpectedType):
a, sn, bf = analyze(pat, Buffer(["test --name '123", 1]))
1 change: 0 additions & 1 deletion tests/sistana/test_mix.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from elaina_segment import Buffer

from arclet.alconna.sistana import Fragment, SubcommandPattern
from arclet.alconna.sistana.analyzer import LoopflowExitReason
from arclet.alconna.sistana.model.receiver import Rx
from arclet.alconna.sistana.some import Value

Expand Down

0 comments on commit a25ca9d

Please sign in to comment.