Compare commits

..

No commits in common. "f52b9631546f7b1c2c7f124a5b61b54f6a53e85b" and "dc3cc6427b6dbf9158c2592c90eded86194d5c32" have entirely different histories.

4 changed files with 49 additions and 151 deletions

View file

@ -1,58 +0,0 @@
#+TITLE: Tutor
A collection manager for Magic: The Gathering playing cards.
* Searching
Text in the search bar will be used to filter cards having that text in their
name. Additionally, the keyword expressions below can be used to search for
cards with certain properties.
** Examples
- ~bolt~ :: Find all cards with "bolt" in the name
- ~"God of"~ :: Find all cards with "God of" in the name
- ~t:legendary t:creature c:jund~ :: Find all legendary creatures with a color
identity of red/blue/green
- ~color<=ubg~ :: Find all spells that are blue, black, green, or any
combination thereof.
- ~color:red set:stx rarity>=rare~ :: Find all red cards in Strixhaven that are
rare or mythic
- ~t:enchantment o:"enters the battlefield"~ :: Find all enchantments with ETB
effects
** Keywords
*** Colors
- Keywords :: =c=, =color=
- Operators :: ~:~ (matches), ~>=~ (greater than or equal to), ~<=~ (less than
or equal to)
Matches cards of the chosen color or colors.
- Single colors :: =w= or =white=, =u= or =blue=, =b= or =black, =g= or =green=, =r= or =red=
- Any combination of abbreviated single colors :: e.g.: =rg=, =uw=, or =wubgr=
- Ravnican guilds :: =boros= (white/red), =golgari= (green/black), =selesnya=
(green/white), =dimir= (blue/black), =orzhov= (white/black), =izzet=
(blue/red), =gruul= (red/green), =azorius= (white/blue), =rakdos= (black/red),
=simic= (green/blue)
- Alaran shards :: =bant= (white/green/blue), =esper= (blue/white/black),
=grixis= (black/blue/red), =jund= (red/blue/green), =naya= (green/red/white)
- Tarkirian wedges :: =abzan= (white/black/green), =jeskai= (white/blue/red),
=sultai= (blue/black/green), =mardu= (white/black/red), =temur=
(blue/red/green)
*** Sets
- Keywords :: =s=, =set=, =e=, =expansion=
- Operators :: ~:~ (matches)
*** Rarity
- Keywords :: =r=, =rarity=
- Operators :: ~:~ (matches), ~>=~ (greater than or equal to), ~<=~ (less than
or equal to)
*** Type
- Keywords :: =t=, =type=
- Operators :: ~:~ (matches)
*** Oracle Text
- Keywords :: =o=, =oracle=
- Operators :: ~:~ (matches)

0
README.rst Normal file
View file

View file

@ -102,56 +102,38 @@ async def advanced_search(
params = {}
sets = []
logger.debug("Performing search for: %s", search)
logging.debug("Performing search for: %s", search)
for i, criterion in enumerate(search.criteria):
param = f"param_{i}"
if isinstance(criterion, tutor.search.Name):
constraints.append(f"cards.name LIKE :{param}")
params[param] = f"%{criterion.text}%"
params[param] = f"%{criterion.name}%"
if isinstance(criterion, tutor.search.Type):
constraints.append(f"cards.type_line LIKE :{param}")
params[param] = f"%{criterion.text}%"
if isinstance(criterion, tutor.search.Expansion):
params[param] = f"%{criterion.name}%"
if isinstance(criterion, tutor.search.IsExpansion):
constraints.append(f"cards.set_code LIKE :{param}")
params[param] = criterion.set_code
if isinstance(criterion, tutor.search.Color):
if criterion.operator == tutor.search.Operator.matches:
if isinstance(criterion, tutor.search.InExpansion):
sets.append(criterion.set_code)
if isinstance(criterion, tutor.search.IsColor):
constraints.append(f"cards.color_identity LIKE :{param}")
params[param] = tutor.models.Color.to_string(criterion.colors)
if criterion.operator == tutor.search.Operator.lte:
constraints.append(
"({})".format(
" OR ".join(
[
f"cards.color_identity LIKE :{param}_{color}"
for color in criterion.colors
]
)
)
)
params.update(
{f"{param}_{color}": str(color) for color in criterion.colors}
)
params[param] = tutor.models.Color.to_string(criterion.colors)
if criterion.operator == tutor.search.Operator.gte:
constraints.append(f"cards.color_identity LIKE :{param}")
params[param] = "%{}%".format(
"%".join(tutor.models.Color.to_string(criterion.colors))
)
if isinstance(criterion, tutor.search.Rarity):
if criterion.operator == tutor.search.Operator.matches:
if isinstance(criterion, tutor.search.IsRarity):
constraints.append(f"cards.rarity LIKE :{param}")
params[param] = str(criterion.rarity)
if criterion.operator == tutor.search.Operator.lte:
constraints.append(f"rarities.rarity_ord <= :{param}")
params[param] = criterion.rarity.value
if criterion.operator == tutor.search.Operator.gte:
constraints.append(f"rarities.rarity_ord >= :{param}")
params[param] = criterion.rarity.value
if isinstance(criterion, tutor.search.Oracle):
constraints.append(f"cards.oracle_text LIKE :{param}")
params[param] = f"%{criterion.text}%"
if sets:
set_params = {f"set_{i}": set_code for i, set_code in enumerate(sets)}
constraints.append(
"cards.set_code IN ({})".format(
", ".join([f":{key}" for key in set_params.keys()])
)
)
params.update(set_params)
if in_collection is not None:
if in_collection:
joins.append("JOIN copies ON (cards.scryfall_id = copies.scryfall_id)")
@ -175,7 +157,6 @@ async def advanced_search(
f"LIMIT {offset},{limit}",
]
)
logger.debug("Query: %s", (query, params))
cursor = await db.execute(query, params)
rows = await cursor.fetchall()
return [

View file

@ -1,5 +1,4 @@
import dataclasses
import enum
import functools
import typing
@ -8,35 +7,38 @@ import parsy
import tutor.models
class Operator(enum.Enum):
matches = ":"
gte = ">="
lte = "<="
@dataclasses.dataclass
class Criterion:
operator: Operator
...
@dataclasses.dataclass
class Expansion(Criterion):
class IsExpansion(Criterion):
set_code: parsy.string
@dataclasses.dataclass
class Color(Criterion):
class InExpansion(Criterion):
set_code: parsy.string
@dataclasses.dataclass
class IsColor(Criterion):
colors: typing.Set[tutor.models.Color]
@dataclasses.dataclass
class Rarity(Criterion):
class IsRarity(Criterion):
rarity: tutor.models.Rarity
@dataclasses.dataclass
class Name(Criterion):
name: parsy.string
@dataclasses.dataclass
class Type(Criterion):
text: parsy.string
name: parsy.string
@dataclasses.dataclass
@ -44,12 +46,6 @@ class Oracle(Criterion):
text: parsy.string
@dataclasses.dataclass
class Name(Criterion):
operator: Operator = Operator.matches
text: parsy.string = ""
@dataclasses.dataclass
class Search:
criteria: typing.List[Criterion]
@ -67,10 +63,7 @@ W, U, B, G, R = (
tutor.models.Color.Green,
tutor.models.Color.Red,
)
matches = parsy.string(":").map(Operator)
gte = parsy.string(">=").map(Operator)
lte = parsy.string("<=").map(Operator)
matches = parsy.string(":")
color = (
ustring("w").result(W)
@ -119,56 +112,38 @@ wedge = (
| lstring("temur").result({U, R, G})
)
any_color = single_color | guild | shard | wedge | multicolor
colors = parsy.seq(
_keyword=lstring_from("c", "color", "colors"),
operator=matches | gte | lte,
colors=any_color,
).combine_dict(Color)
colors = single_color | guild | shard | wedge | multicolor
expansion_string = (
parsy.regex(r"[a-zA-Z0-9]+").map(lambda s: s.upper()).desc("expansion set code")
)
expansion = parsy.seq(
_keyword=lstring_from("e", "expansion", "s", "set"),
operator=matches,
set_code=expansion_string,
).combine_dict(Expansion)
is_expansion = lstring_from("e", "expansion", "s", "set") >> matches >> expansion_string.map(IsExpansion)
# in_expansion = lstring("in") >> matches >> expansion_string.map(InExpansion)
in_expansion = lstring("in") >> matches >> expansion_string.map(InExpansion)
any_rarity = (
rarity = (
lstring_from("c", "common").result(tutor.models.Rarity.Common)
| lstring_from("u", "uncommon").result(tutor.models.Rarity.Uncommon)
| lstring_from("r", "rare").result(tutor.models.Rarity.Rare)
| lstring_from("m", "mythic").result(tutor.models.Rarity.Mythic)
)
rarity = parsy.seq(
_keyword=lstring_from("r", "rarity"),
operator=matches | gte | lte,
rarity=any_rarity,
).combine_dict(Rarity)
is_rarity = lstring_from("r", "rarity") >> matches >> rarity.map(IsRarity)
is_color = lstring_from("c", "color", "colors") >> matches >> colors.map(IsColor)
string_literal = parsy.regex(r'"[^"]*"').map(lambda s: s[1:-1]) | parsy.regex(r"[^\s]+")
type_line = parsy.seq(
_keyword=lstring_from("t", "type"),
operator=matches,
text=string_literal,
).combine_dict(Type)
has_type = lstring_from("t", "type") >> matches >> string_literal.map(Type)
oracle = parsy.seq(
_keyword=lstring_from("o", "oracle"),
operator=matches,
text=string_literal,
).combine_dict(Oracle)
has_oracle = lstring_from("o", "oracle") >> matches >> string_literal.map(Oracle)
name = parsy.seq(text=string_literal).combine_dict(Name)
name = string_literal.map(Name)
criterion = colors | expansion | rarity | type_line | oracle | name
criterion = (
is_expansion | in_expansion | is_rarity | is_color | has_type | has_oracle | name
)
padding = parsy.regex(r"\s*")
search = padding >> (criterion << padding).many().map(Search)