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

View file

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