Skip to content

Commit 0e91a59

Browse files
authored
Merge pull request #1852 from UlrichB22/fullsearch
Add FullSearch macro migration for moin1.9 categories
2 parents 56566b3 + cca3c3b commit 0e91a59

File tree

4 files changed

+147
-3
lines changed

4 files changed

+147
-3
lines changed

src/moin/cli/migration/moin19/import19.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
# individual macro migrations register with the migrate_macros module
3030
from .macros import MonthCalendar # noqa
31+
from .macros import FullSearch # noqa
3132
from .macros import PageList # noqa
3233

3334
from moin.app import create_app
@@ -726,13 +727,24 @@ def process_categories(meta, data, item_category_regex):
726727
matches = list(item_category_regex.finditer(categories))
727728
if matches:
728729
data = data[:-end] # remove the ---- line from the content
729-
tags = [_m.group("all") for _m in matches]
730+
tags = []
731+
# remove the closing bracket if the category is written as a link
732+
for m in matches:
733+
tag = m.group("all")
734+
if tag[-2:] == "]]":
735+
tags.append(tag[:-2])
736+
else:
737+
tags.append(tag)
730738
meta.setdefault(TAGS, []).extend(tags)
731739
# remove everything between first and last category from the content
732740
# unexpected text before and after categories survives, any text between categories is deleted
733741
start = matches[0].start()
734742
end = matches[-1].end()
735-
rest = categories[:start] + categories[end:]
743+
# remove the opening bracket if the category is written as a link
744+
if start >= 2 and categories[start - 2 : start] == "[[":
745+
rest = categories[: start - 2] + categories[end:]
746+
else:
747+
rest = categories[:start] + categories[end:]
736748
data += "\r\n" + rest.lstrip()
737749
data = data.rstrip() + "\r\n"
738750
return data
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Copyright: 2025 MoinMoin:UlrichB
2+
# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
3+
4+
"""
5+
MoinMoin CLI - Migration of FullSearch macro (moin1.9) to new ItemList macro (moin2) if possible
6+
"""
7+
8+
from moin.cli.migration.moin19 import macro_migration
9+
from moin.utils.tree import moin_page
10+
11+
CONTENT_TYPE_MACRO_FORMATTER = "x-moin/macro;name={}"
12+
MACRO_NAME_FULLSEARCH = "FullSearch"
13+
MACRO_NAME_FULLSEARCH_CACHED = "FullSearchCached"
14+
15+
16+
def convert_fullsearch_macro_to_item_list(node):
17+
"""Convert the given FullSearch macro node to an ItemList macro in-place
18+
19+
The moin1.9 FullSearch macro is used in various situations. One case
20+
is the listing of all pages for a specific category. In moin2, the
21+
categories are replaced by tags. Therefore, all macro calls related to
22+
categories are converted to the moin2 macro ItemList.
23+
24+
In all other cases, the FullSearch macro call is not changed.
25+
The same applies to the FullSearchCached macro.
26+
27+
Example conversions:
28+
29+
| PageList macro (moin1.9) | ItemList macro (moin2) |
30+
|-----------------------------------------|----------------------------------------------|
31+
| <<FullSearch(CategorySample)>> | <<ItemList(item="/", tag="CategorySample")>> |
32+
| <<FullSearch(category:CategorySample)>> | <<ItemList(item="/", tag="CategorySample")>> |
33+
34+
:param node: the DOM node matching the FullSearch macro content type
35+
:type node: emeraldtree.tree.Element
36+
"""
37+
38+
# arguments
39+
args_before = None
40+
for elem in node.iter_elements():
41+
if elem.tag.name == "arguments":
42+
args_before = elem.text
43+
if args_before and args_before.startswith("Category"):
44+
# argument is a category name, lets migrate to ItemList macro
45+
args_after = f'item="/", tag="{args_before}"'
46+
elif args_before and args_before.startswith("category:"):
47+
# argument is a category name, strip keyword and migrate to ItemList macro
48+
args_after = f'item="/", tag="{args_before[9:]}"'
49+
else:
50+
# argument is not a category or empty, we keep the old FullSearch macro
51+
return
52+
53+
# content type
54+
new_content_type = CONTENT_TYPE_MACRO_FORMATTER.format("ItemList")
55+
node.set(moin_page.content_type, new_content_type)
56+
57+
for elem in node.iter_elements():
58+
if elem.tag.name == "arguments":
59+
elem.clear()
60+
elem.append(args_after)
61+
62+
# 'alt' attribute
63+
new_alt = f"<<ItemList({args_after})>>"
64+
node.set(moin_page.alt, new_alt)
65+
66+
67+
macro_migration.register_macro_migration(
68+
CONTENT_TYPE_MACRO_FORMATTER.format(MACRO_NAME_FULLSEARCH), convert_fullsearch_macro_to_item_list
69+
)
70+
71+
macro_migration.register_macro_migration(
72+
CONTENT_TYPE_MACRO_FORMATTER.format(MACRO_NAME_FULLSEARCH_CACHED), convert_fullsearch_macro_to_item_list
73+
)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright: 2025 MoinMoin:UlrichB
2+
# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
3+
4+
"""
5+
MoinMoin - moin.cli.migration.moin19.macros Test FullSearch
6+
"""
7+
8+
import pytest
9+
10+
from moin.converters.moinwiki19_in import ConverterFormat19
11+
12+
from moin.cli.migration.moin19 import import19
13+
from moin.cli.migration.moin19.macro_migration import migrate_macros
14+
15+
from moin.utils.tree import moin_page
16+
17+
18+
@pytest.mark.parametrize(
19+
"legacy_macro,expected_args",
20+
[
21+
("<<FullSearch(CategorySample)>>", 'item="/", tag="CategorySample"'),
22+
("<<FullSearch(category:CategorySample)>>", 'item="/", tag="CategorySample"'),
23+
("<<FullSearchCached(CategoryTest)>>", 'item="/", tag="CategoryTest"'),
24+
],
25+
)
26+
def test_macro_conversion_itemlist(legacy_macro, expected_args):
27+
# macro calls for Categories
28+
converter = ConverterFormat19()
29+
dom = converter(legacy_macro, import19.CONTENTTYPE_MOINWIKI)
30+
migrate_macros(dom) # in-place conversion
31+
32+
body = list(dom)[0]
33+
part = list(body)[0]
34+
35+
assert part.get(moin_page.content_type) == "x-moin/macro;name=ItemList"
36+
assert part.get(moin_page.alt) == f"<<ItemList({expected_args})>>"
37+
38+
39+
@pytest.mark.parametrize(
40+
"legacy_macro,expected_args",
41+
[
42+
("<<FullSearch()>>", ""),
43+
("<<FullSearch(Calendar/2025-01-01)>>", "Calendar/2025-01-01"),
44+
("<<FullSearch('AnyText')>>", "'AnyText'"),
45+
],
46+
)
47+
def test_macro_conversion_fullsearch(legacy_macro, expected_args):
48+
# macro calls other than Categories are not changed
49+
converter = ConverterFormat19()
50+
dom = converter(legacy_macro, import19.CONTENTTYPE_MOINWIKI)
51+
migrate_macros(dom) # in-place conversion
52+
53+
body = list(dom)[0]
54+
part = list(body)[0]
55+
56+
assert part.get(moin_page.content_type) == "x-moin/macro;name=FullSearch"
57+
assert part.get(moin_page.alt) == f"<<FullSearch({expected_args})>>"

src/moin/macros/ItemList.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,10 @@ def macro(self, content, arguments, page_url, alternative):
127127
if item.startswith("+modify/"):
128128
item = item.split("/", 1)[1]
129129

130+
if item == "/":
131+
item = ""
130132
# verify item exists and current user has read permission
131-
if item != "":
133+
elif item != "":
132134
if not flaskg.storage.get_item(**(split_fqname(item).query)):
133135
err_msg = _("Item does not exist or read access blocked by ACLs: {0}").format(item)
134136
return fail_message(err_msg, alternative)

0 commit comments

Comments
 (0)