Skip to content
This repository was archived by the owner on Oct 1, 2024. It is now read-only.

Commit 50af200

Browse files
committed
Query refactor and corrections, with tests.
1 parent 164326f commit 50af200

File tree

2 files changed

+93
-69
lines changed

2 files changed

+93
-69
lines changed

polyplug.py

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,62 @@
1010

1111
class Query:
1212
"""
13+
Encapsulates a query to match elements in the DOM.
14+
1315
A query must have one, and only one, of:
1416
1517
* id - a unique element id (e.g. "myElementId").
16-
* tag - a tag name (e.g. "p").
1718
* classname - a class (e.g. "my-css-class").
19+
* tag - a tag name (e.g. "p").
1820
* css - a css selector (e.g "p.className").
1921
2022
These types of query relate to the four ways in which elements in an HTML
2123
document may be identified.
2224
25+
A Query is instantiated with a string using the following common syntax
26+
for identifying the different types of query:
27+
28+
* #id-name - Starting with a hash indicates an element's unique id.
29+
* .class-name - Starting with a full stop indicates a CSS class name.
30+
* p - A string of only alphabetical characters is treated as a tag name.
31+
* p.classname - Anything else is assumed to be a css selector.
32+
2333
E.g.
2434
25-
q = Query(id="myElementID")
35+
q = Query(".myElementID")
2636
"""
2737

28-
# Query type definitions
29-
ID = "id"
30-
TAG = "tag"
31-
CLASSNAME = "classname"
32-
CSS = "css"
33-
QUERY_TYPES = (ID, TAG, CLASSNAME, CSS)
34-
35-
def __init__(self, **kwargs):
38+
def __init__(self, query):
3639
"""
37-
Raise a ValueError if it's not the case that one, and only one of the
38-
expected types of query is given.
40+
Validate and interpret the query string.
3941
"""
40-
query_type = [key for key in kwargs if key in self.QUERY_TYPES]
41-
if len(query_type) == 1:
42-
self.query_type = query_type[0]
43-
setattr(self, self.query_type, kwargs[self.query_type])
42+
self.raw_query = query
43+
target = None
44+
if query[:1] == "#":
45+
# Unique id.
46+
target = query[1:]
47+
if target:
48+
self.query_type = "id"
49+
else:
50+
raise ValueError("Invalid id.")
51+
elif query[:1] == ".":
52+
# CSS class.
53+
target = query[1:]
54+
if target:
55+
self.query_type = "classname"
56+
else:
57+
raise ValueError("Invalid class.")
58+
elif query.isalpha():
59+
# tagName.
60+
target = query
61+
self.query_type = "tag"
62+
else:
63+
# CSS selector.
64+
target = query
65+
self.query_type = "css"
66+
67+
if target:
68+
setattr(self, self.query_type, target)
4469
else:
4570
raise ValueError("Bad query specification.")
4671

@@ -443,24 +468,24 @@ def find(self, selector):
443468
representing nodes that match "selector" string. The selector
444469
string understands:
445470
446-
* .my-id - returns the first ElementNode with the id "my-id". Returns
471+
* #my-id - returns the first ElementNode with the id "my-id". Returns
447472
None if no match is found.
448-
* #my-class - returns a list containing elements with the class
473+
* .my-class - returns a list containing elements with the class
449474
"my-class". Returns an empty list if no match is found.
450475
* li - returns a list containing elements with the tagName "li".
451476
Returns an empty list if no match is found.
452477
"""
453478
# Validate selector.
454479
if not selector:
455480
raise ValueError("Missing selector.")
456-
if selector[0] == ".":
481+
if selector[0] == "#":
457482
# Select by unique id.
458483
target_id = selector[1:]
459484
if target_id:
460485
return self._find_by_id(target_id)
461486
else:
462487
raise ValueError("Invalid id.")
463-
elif selector[0] == "#":
488+
elif selector[0] == ".":
464489
# Select by css class.
465490
target_class = selector[1:]
466491
if target_class:
@@ -615,7 +640,7 @@ def get_listener_id(query, event_type, listener):
615640
Given a query, event type and listener function, generate a unique id from
616641
this combination.
617642
"""
618-
raw = str(query.as_dict) + event_type + listener.__name__
643+
raw = query.raw_query + event_type + listener.__name__
619644
return binascii.hexlify(
620645
hashlib.sha256(raw.encode("utf-8")).digest()
621646
).decode("ascii")
@@ -637,11 +662,12 @@ def update(query, target):
637662
Update the DOM so the node[s] matching the query are mutated to the state
638663
defined by the target node.
639664
"""
665+
q = Query(query)
640666
builtins.print(
641667
json.dumps(
642668
{
643669
"type": "updateDOM",
644-
"query": query.as_dict,
670+
"query": q.as_dict,
645671
"target": target.as_dict,
646672
}
647673
)
@@ -653,13 +679,14 @@ def remove(query, event_type, listener):
653679
Remove the referenced listener from handling the event_type from the
654680
node[s] matching the query.
655681
"""
656-
listener_id = get_listener_id(query, event_type, listener)
682+
q = Query(query)
683+
listener_id = get_listener_id(q, event_type, listener)
657684
del LISTENERS[listener_id]
658685
builtins.print(
659686
json.dumps(
660687
{
661688
"type": "removeEvent",
662-
"query": query.as_dict,
689+
"query": q.as_dict,
663690
"eventType": event_type,
664691
}
665692
)
@@ -679,6 +706,7 @@ def plug(query, event_type):
679706
This decorator wrapper function creates a closure in which various
680707
contextual aspects are contained and run.
681708
"""
709+
q = Query(query)
682710

683711
def decorator(fn):
684712
"""
@@ -688,12 +716,12 @@ def decorator(fn):
688716
def wrapper(event):
689717
return fn(event)
690718

691-
listener_id = get_listener_id(query, event_type, wrapper)
719+
listener_id = get_listener_id(q, event_type, wrapper)
692720
builtins.print(
693721
json.dumps(
694722
{
695723
"type": "registerEvent",
696-
"query": query.as_dict,
724+
"query": q.as_dict,
697725
"eventType": event_type,
698726
"listener": listener_id,
699727
}

0 commit comments

Comments
 (0)