Skip to content

Commit

Permalink
py: search subnode by path and insert new content
Browse files Browse the repository at this point in the history
  • Loading branch information
haxscramper committed Mar 17, 2024
1 parent b9d2571 commit 407e73c
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 14 deletions.
57 changes: 45 additions & 12 deletions src/sem/SemBaseApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,15 @@ bool OrgDocumentSelector::isMatching(
const SemId<Org>& node,
const Vec<SubnodeVisitorCtxPart>& ctx) const {

for (int i = 0; i < path.size(); ++i) {
if (i < path.high() && !path.at(i).link.has_value()) {
throw std::logic_error(
fmt("Element at index {} is not a final element in the "
"selector path, but it is missing a link.",
i));
}
}

using PathIter = Vec<OrgSelectorCondition>::const_iterator;

auto dbg =
Expand All @@ -426,17 +435,18 @@ bool OrgDocumentSelector::isMatching(

auto getParentSpan = [](int levels, Span<SubnodeVisitorCtxPart> span)
-> Opt<Span<SubnodeVisitorCtxPart>> {
auto it = span.rbegin();
auto end = span.rbegin();
auto begin = span.rend();
int skippedLevels = 0;
while (it != span.rend() && skippedLevels != levels) {
if (it->node) { ++skippedLevels; }
++it;
while (skippedLevels < levels && end != begin) {
if (end->node) { ++skippedLevels; }
++end;
}

if (it == span.rend()) {
return std::nullopt;
if (skippedLevels == levels) {
return IteratorSpan(begin.base(), end.base());
} else {
return IteratorSpan(span.begin(), it.base());
return std::nullopt;
}
};

Expand Down Expand Up @@ -466,22 +476,41 @@ bool OrgDocumentSelector::isMatching(
SemId<Org> node,
Span<SubnodeVisitorCtxPart> ctx,
int depth) {
dbg(fmt("condition={} node={}", condition->debug, node->getKind()),
depth);
if (debug) {
Vec<Str> context;
for (auto const& it : ctx) {
if (it.node) {
context.push_back(
fmt("{}", it.node.value()->getKind()));
} else {
context.push_back(fmt("idx={}", it.index));
}
}

dbg(fmt("condition={} (@{}/{}) node={} ctx={}",
condition->debug,
std::distance(path.begin(), condition),
path.high(),
node->getKind(),
context),
depth);
}

if (condition->check(node, ctx)) {
dbg("passed check", depth);
dbg(fmt("passed check {}", condition->debug), depth);
if (condition->link) {
auto const& link = condition->link.value();
dbg(fmt("link={}", link.kind), depth);
switch (link.kind) {
case OrgSelectorLink::Kind::DirectSubnode: {
auto node = getParentNode(1, ctx);
if (node) {
return aux(
bool result = aux(
condition + 1,
node.value(),
getParentSpan(1, ctx).value(),
depth + 1);
return result;
} else {
return false;
}
Expand All @@ -492,7 +521,9 @@ bool OrgDocumentSelector::isMatching(
while (
auto parentSpan = getParentSpan(offset, ctx)) {
auto parent = getParentNode(offset, ctx);
if (aux(condition + 1,
if (parent
&& aux(
condition + 1,
parent.value(),
parentSpan.value(),
depth + 1)) {
Expand All @@ -505,6 +536,7 @@ bool OrgDocumentSelector::isMatching(
}
}
} else {
dbg(fmt("no link"), depth);
return true;
}
} else {
Expand Down Expand Up @@ -545,6 +577,7 @@ void OrgDocumentSelector::searchSubtreePlaintextTitle(
const Str& title,
Opt<OrgSelectorLink> link) {
assertLinkPresence();
if (debug) { LOG(INFO) << title; }
path.push_back({
.check = [title](
SemId<Org> const& node,
Expand Down
19 changes: 19 additions & 0 deletions tests/org/tOrgE2EParse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,24 @@ Paragraph under subtitle 2
}
}

TEST(OrgDocumentSelector, GetSubtreeAtPath) {
auto node = parseNode(R"(
* Title1
** Subtitle1
Content1
** Subtitle2
Content2
* Title2
)");

sem::OrgDocumentSelector selector;
selector.searchSubtreePlaintextTitle(
"Subtitle1", selector.linkIndirectSubnode());
selector.searchSubtreePlaintextTitle("Title1");
auto matches = selector.getMatches(node);
EXPECT_EQ(matches.size(), 1);
}

TEST(OrgApi, EachSubnodeWithContext) {
auto node = parseNode(R"(*bold*)");
Vec<Pair<sem::SemId<sem::Org>, Vec<sem::SubnodeVisitorCtxPart>>> ctx;
Expand All @@ -492,6 +510,7 @@ TEST(OrgApi, EachSubnodeWithContext) {
EXPECT_EQ(ctx.at(1).second.at(1).index.value(), 0);
}


TEST(SimpleNodeConversion, LCSCompile) {
Vec<int> first{1, 2, 3};
Vec<int> second{1, 2, 3};
Expand Down
46 changes: 44 additions & 2 deletions tests/python/test_simple_org_use.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from py_scriptutils.script_logging import log

osk = org.OrgSemKind
CAT = "test_simple_org_use.py"


def test_word() -> None:
assert org.Document
Expand All @@ -29,17 +31,18 @@ def test_link_resolution():
:id: id-name
:end:
""")

resolve.addNodes(node)
assert len(resolve.getSubtreeById("id-name")) == 1, org.treeRepr(node)


def test_subnode_visitor():
node = org.parseString("Word")
kinds = []
org.eachSubnodeRec(node, lambda it: kinds.append(it.getKind()))
assert kinds == [osk.Document, osk.Paragraph, osk.Word], kinds


def test_subnode_selector():
node = org.parseString("Word")
selector = org.OrgDocumentSelector()
Expand All @@ -48,3 +51,42 @@ def test_subnode_selector():
assert len(matches) == 1
assert matches[0].getKind() == osk.Word
assert matches[0].text == "Word"


def test_procedural_subtree_edits():
node = org.parseString("""
* Title1
** Subtitle1
Content1
** Subtitle2
Content2
* Title2
""")

def ensure_content(subtree_path: List[str], content: str):
def get_selector_at_path(path: List[str]):
selector = org.OrgDocumentSelector()
for idx, title in enumerate(path):
selector.searchSubtreePlaintextTitle(
title=title,
link=None if idx == len(path) - 1 else selector.linkIndirectSubnode())

return selector

matches = get_selector_at_path(subtree_path[::-1]).getMatches(node)
if not matches:
parent = get_selector_at_path(subtree_path[:1][::-1]).getMatches(node)
assert len(parent) == 1
parent[0].push_back(
org.Subtree(
title=org.Paragraph(subnodes=[org.Word(text=subtree_path[-1])]),
subnodes=[org.Paragraph(subnodes=[org.Word(text=content)])]))

ensure_content(["Title1", "Subtitle2"], "wont_be_added")
ensure_content(["Title1", "Subtitle2"], "wont_be_added")
ensure_content(["Title1", "Subtitle2"], "wont_be_added")
ensure_content(["Title2", "Subtitle3"], "new_content")

text = org.formatToString(node)
assert "wont_be_added" not in text
assert "new_content" in text

0 comments on commit 407e73c

Please sign in to comment.