Skip to content

Commit

Permalink
✨ πŸ“ navigation: next and previous
Browse files Browse the repository at this point in the history
  • Loading branch information
boisgera committed May 6, 2022
1 parent d15b804 commit e38c787
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 15 deletions.
95 changes: 92 additions & 3 deletions mkdocs/labs.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ At this stage, the query only contains the document itself.
- Pandoc(Meta({}), [Para([Str('Hello'), Space(), Str('world!')])])
```

### Types

The `find` method allows to select items within the initial collection.
To begin with, we can search items by type:

Expand Down Expand Up @@ -137,6 +139,15 @@ If the list of arguments is empty, there is no match:
>>> q.find()
<BLANKLINE>
```
In a boolean context, a query with no results is considered `False`

```python
>>> bool(q.find())
False
>>> if not q.find():
... print("no result")
no result
```

To add match several conditions at once, the `filter` method can be used:

Expand All @@ -145,18 +156,25 @@ To add match several conditions at once, the `filter` method can be used:
- Str('Hello')
- Str('world!')
```
The `filter` method can be used implicitly: a query is callable
```python
>>> q.find(Inline)(Str)
- Str('Hello')
- Str('world!')
```

We can also match the negation of a condition

```python
>>> q.find(Inline).filter(not_(Space))
>>> q.find(Inline)(not_(Space))
- Str('Hello')
- Str('world!')
```

### Predicates
### Tests

Type are not the only conditions we can check, predicates can be used too:
Types are not the only possible selectors, predicates -- functions that take
a pandoc element and return a boolean value -- can be used too:

```python
>>> def startswith_H(elt):
Expand All @@ -166,14 +184,85 @@ Type are not the only conditions we can check, predicates can be used too:
- Str('Hello')
```

You can use predicate to define and select "virtual types" in a document.
For example,

```python
def AttrHolder(elt):
return isinstance(elt, (Code, Link, Image, Span, Div, CodeBlock, Header, Table))
```

**TODO:** match by attributes (id, classes, key-values); use keyword arguments
in find with "or" semantics for lists; allow for predicates. For key values
match, match for key existence, key-value pair, predicate as a whole or just
for value.


### Navigation


**TODO.** Parent, children, next, previous, next_sibling, previous_sibling.

```python
>>> q
- Pandoc(Meta({}), [Para([Str('Hello'), Space(), Str('world!')])])
>>> q.next
- Meta({})
>>> q.next.next
- {}
>>> q.next.next.next
- [Para([Str('Hello'), Space(), Str('world!')])]
>>> q.next.next.next.next
- Para([Str('Hello'), Space(), Str('world!')])
>>> q.next.next.next.next.next
- [Str('Hello'), Space(), Str('world!')]
>>> q.next.next.next.next.next.next
- Str('Hello')
>>> q.next.next.next.next.next.next.next
- 'Hello'
>>> q.next.next.next.next.next.next.next.next
- Space()
>>> q.next.next.next.next.next.next.next.next.next
- Str('world!')
>>> q.next.next.next.next.next.next.next.next.next.next
- 'world!'
>>> q.next.next.next.next.next.next.next.next.next.next.next
<BLANKLINE>
```

```python
>>> q.find(str)
- 'Hello'
- 'world!'
>>> q.find(str)[1]
- 'world!'
>>> w = q.find(str)[1]
>>> w.previous
- Str('world!')
>>> w.previous.previous
- Space()
>>> w.previous.previous.previous
- 'Hello'
>>> w.previous.previous.previous.previous
- Str('Hello')
>>> w.previous.previous.previous.previous.previous
- [Str('Hello'), Space(), Str('world!')]
>>> w.previous.previous.previous.previous.previous.previous
- Para([Str('Hello'), Space(), Str('world!')])
>>> w.previous.previous.previous.previous.previous.previous.previous
- [Para([Str('Hello'), Space(), Str('world!')])]
>>> w.previous.previous.previous.previous.previous.previous.previous.previous
- {}
>>> w.previous.previous.previous.previous.previous.previous.previous.previous.previous
- Meta({})
>>> w.previous.previous.previous.previous.previous.previous.previous.previous.previous.previous
- Pandoc(Meta({}), [Para([Str('Hello'), Space(), Str('world!')])])
>>> w.previous.previous.previous.previous.previous.previous.previous.previous.previous.previous.previous
<BLANKLINE>
```


--------------------------------------------------------------------------------


Nota: finding lists of inlines is difficult; finding *non-empty* lists of
Expand Down
52 changes: 40 additions & 12 deletions src/pandoc/labs.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,14 @@ def _getitem(sequence, indices):
return sequence[indices]

class Query:
def __init__(self, *results):
def __init__(self, results):
self._elts = []
for result in results:
if isinstance(result, Query):
self._elts.extend(result._elts) # Mmmm ownership issues
else: # "raw results": list of (elt, path)
self._elts.extend(result)
if isinstance(results, tuple):
results = [results]
if isinstance(results, Query):
self._elts.extend(results._elts) # Mmmm ownership issues. Copy?
else: # "raw results": list of (elt, path)
self._elts.extend(results)

# ℹ️ The call `find(object)` is public and equivalent to `_iter()`.
def _iter(self):
Expand Down Expand Up @@ -165,6 +166,8 @@ def get_child(self, i):

if isinstance(i, int):
try:
if i < 0:
i += len(elt)
child = children[i]
results.append((child, elt_path.copy() + [(elt, i)]))
except IndexError:
Expand All @@ -185,8 +188,6 @@ def get_parent(self):

parent = property(get_parent)

# 🚧 TODO: next and previous

def get_next(self):
results = []
for elt, elt_path in self._elts:
Expand Down Expand Up @@ -215,6 +216,33 @@ def get_next(self):

next = property(get_next)

# 🚧 TODO: previous

def get_previous(self):
results = []
for elt, elt_path in self._elts:

# TODO: try deeper in the previous sibling first
# or the previous sibling
# or of there is no previous sibling, the parent

previous_sibling = Query([(elt, elt_path)]).previous_sibling
if previous_sibling:
last_child = previous_sibling
while child := last_child.get_child(-1):
last_child = child
pass
results.append(last_child._elts[0])
elif parent := Query([(elt, elt_path)]).parent:
results.append(parent._elts[0])
else:
pass

return Query(results)

previous = property(get_previous)


def get_next_sibling(self):
indices = [path[-1][1] for elt, path in self._elts if path != []]
results = []
Expand All @@ -240,9 +268,9 @@ def get_previous_sibling(self):
pass
return Query(results)

prev_sibling = property(get_previous_sibling)
previous_sibling = property(get_previous_sibling)

# Query container (hide path info)
# Query container (hide path info). Or maybe not? Be more explicit?
# --------------------------------------------------------------------------
def __len__(self):
return len(self._elts)
Expand All @@ -251,9 +279,9 @@ def __bool__(self):
return len(self._elts) != 0

def __getitem__(self, i):
return Query([self._elts[i]])
return Query(self._elts[i])

def __iter__(self):
def __iter__(self): # unwrap or not? Mmmm maybe no.
return (elt for elt, _ in self._elts)

def __repr__(self):
Expand Down

0 comments on commit e38c787

Please sign in to comment.