From efc1e7ad9088694dde7874d399c76bce1dadab82 Mon Sep 17 00:00:00 2001 From: Jessy Giacomoni Date: Fri, 6 Jun 2025 17:58:16 +0200 Subject: [PATCH 1/2] lt's possible with option -t to filter content by "tag" --- src/fitnesse/wikitext/parser/Contents.java | 79 +++-- .../wikitext/shared/ContentsItemBuilder.java | 300 ++++++++++-------- 2 files changed, 203 insertions(+), 176 deletions(-) diff --git a/src/fitnesse/wikitext/parser/Contents.java b/src/fitnesse/wikitext/parser/Contents.java index 68a301efc..6959a60a9 100644 --- a/src/fitnesse/wikitext/parser/Contents.java +++ b/src/fitnesse/wikitext/parser/Contents.java @@ -16,43 +16,52 @@ public Contents() { @Override public Maybe parse(Symbol current, Parser parser) { - Symbol body = parser.parseToEnd(SymbolType.Newline); - for (Symbol child: body.getChildren()) { - if (child.isType(SymbolType.Whitespace)) continue; - String option = child.getContent(); - if (!option.startsWith("-")) return Symbol.nothing; - if (option.equals("-R")) { - current.putProperty(option, String.valueOf(Integer.MAX_VALUE)); - } - else if (option.startsWith("-R")) { - current.putProperty("-R", option.substring(2)); - } - else { - current.putProperty(option, ""); + Symbol body = parser.parseToEnd(SymbolType.Newline); + + for (int i = 0; i < body.getChildren().size(); i++) { + Symbol child = body.getChildren().get(i); + + if (child.isType(SymbolType.Whitespace)) continue; + String option = child.getContent(); + if (!option.startsWith("-")) return Symbol.nothing; + if (option.equals("-R")) { + current.putProperty(option, String.valueOf(Integer.MAX_VALUE)); + } else if (option.startsWith("-R")) { + current.putProperty("-R", option.substring(2)); + } else if (option.equalsIgnoreCase("-t") || option.equalsIgnoreCase("-tag")) { + if (i + 1 < body.getChildren().size()) { + Symbol tagValueSymbol = body.getChildren().get(i + 1); + if (!tagValueSymbol.isType(SymbolType.Whitespace)) { + current.putProperty("-tag", tagValueSymbol.getContent()); + i++; } + } + } else { + current.putProperty(option, ""); } + } - current.copyVariables(new String[] { - Names.HELP_TOC, - Names.HELP_INSTEAD_OF_TITLE_TOC, - Names.REGRACE_TOC, - Names.PROPERTY_TOC, - Names.FILTER_TOC, - Names.MORE_SUFFIX_TOC, - Names.PROPERTY_CHARACTERS, - Names.TEST_PAGE_COUNT_TOC}, - parser.getVariableSource()); - - return new Maybe<>(current); - } - @Override - public String toTarget(Translator translator, Symbol symbol) { - ContentsItemBuilder itemBuilder - = new ContentsItemBuilder(symbol, 1, translator.getPage()); - HtmlTag contentsDiv = new HtmlTag("div"); - contentsDiv.addAttribute("class", "contents"); - contentsDiv.add(HtmlUtil.makeBold("Contents:")); - contentsDiv.add(itemBuilder.buildLevel(translator.getPage())); - return contentsDiv.html(); + current.copyVariables(new String[]{ + Names.HELP_TOC, + Names.HELP_INSTEAD_OF_TITLE_TOC, + Names.REGRACE_TOC, + Names.PROPERTY_TOC, + Names.FILTER_TOC, + Names.MORE_SUFFIX_TOC, + Names.PROPERTY_CHARACTERS, + Names.TEST_PAGE_COUNT_TOC + }, parser.getVariableSource()); + + return new Maybe<>(current); } + + public String toTarget(Translator translator, Symbol symbol) { + ContentsItemBuilder itemBuilder + = new ContentsItemBuilder(symbol, 1, translator.getPage()); + HtmlTag contentsDiv = new HtmlTag("div"); + contentsDiv.addAttribute("class", "contents"); + contentsDiv.add(HtmlUtil.makeBold("Contents:")); + contentsDiv.add(itemBuilder.buildLevel(translator.getPage())); + return contentsDiv.html(); + } } diff --git a/src/fitnesse/wikitext/shared/ContentsItemBuilder.java b/src/fitnesse/wikitext/shared/ContentsItemBuilder.java index 7a9872387..35174b439 100644 --- a/src/fitnesse/wikitext/shared/ContentsItemBuilder.java +++ b/src/fitnesse/wikitext/shared/ContentsItemBuilder.java @@ -9,171 +9,189 @@ import fitnesse.wikitext.SourcePage; import util.GracefulNamer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; +import java.util.*; +import java.util.stream.Collectors; public class ContentsItemBuilder { - private final PropertySource contents; - private final int level; - private final SourcePage page; - - public ContentsItemBuilder(PropertySource contents, int level) { - this(contents, level, null); + private final PropertySource contents; + private final int level; + private final SourcePage page; + + public ContentsItemBuilder(PropertySource contents, int level) { + this(contents, level, null); + } + + public ContentsItemBuilder(PropertySource contents, int level, SourcePage page) { + this.contents = contents; + this.level = level; + this.page = page; + } + + public HtmlTag buildLevel(SourcePage page) { + HtmlTag list = new HtmlTag("ul"); + list.addAttribute("class", "toc" + level); + Optional tagFilter = contents.findProperty("-tag"); + for (SourcePage child : getSortedChildren(page)) { + if (!tagFilter.isPresent() || pageContainsTag(child, tagFilter.get())) { + list.add(buildListItem(child)); + } } + return list; + } - public ContentsItemBuilder(PropertySource contents, int level, SourcePage page) { - this.contents = contents; - this.level = level; - this.page = page; - } - public HtmlTag buildLevel(SourcePage page) { - HtmlTag list = new HtmlTag("ul"); - list.addAttribute("class", "toc" + level); - for (SourcePage child: getSortedChildren(page)) { - list.add(buildListItem(child)); - } - return list; + private HtmlTag buildListItem(SourcePage child) { + HtmlTag listItem = buildItem(child); + if (!child.getChildren().isEmpty()) { + if (level < getRecursionLimit()) { + listItem.add(new ContentsItemBuilder(contents, level + 1, child).buildLevel(child)); + } else if (getRecursionLimit() > 0) { + listItem.add(contents.findProperty(Names.MORE_SUFFIX_TOC, Names.MORE_SUFFIX_DEFAULT)); + } } - - private HtmlTag buildListItem(SourcePage child) { - HtmlTag listItem = buildItem(child); - if (!child.getChildren().isEmpty()) { - if (level < getRecursionLimit()) { - listItem.add(new ContentsItemBuilder(contents, level + 1, child).buildLevel(child)); - } - else if (getRecursionLimit() > 0){ - listItem.add(contents.findProperty(Names.MORE_SUFFIX_TOC, Names.MORE_SUFFIX_DEFAULT)); - } - } - return listItem; + return listItem; + } + + private Collection getSortedChildren(SourcePage parent) { + ArrayList result = new ArrayList<>(parent.getChildren()); + Collections.sort(result); + return result; + } + + public HtmlTag buildItem(SourcePage page) { + HtmlTag listItem = new HtmlTag("li"); + HtmlTag link = new HtmlTag("a", buildBody(page)); + link.addAttribute("href", buildReference(page)); + link.addAttribute("class", getBooleanPropertiesClasses(page)); + listItem.add(link); + String help = page.getProperty(WikiPageProperty.HELP); + if (!help.isEmpty()) { + if (hasOption("-h", Names.HELP_TOC)) { + listItem.add(HtmlUtil.makeSpanTag("pageHelp", ": " + help)); + } else if (hasOption("-H", Names.HELP_INSTEAD_OF_TITLE_TOC)) { + link.use(help); + } else { + link.addAttribute("title", help); + } } + return listItem; + } - private Collection getSortedChildren(SourcePage parent) { - ArrayList result = new ArrayList<>(parent.getChildren()); - Collections.sort(result); - return result; - } + private boolean isSpecialPageToBeCountedAsTest(SourcePage page) { + String pageName = page.getName(); + return pageName.contains("SuiteSetUp") || pageName.contains("SuiteTearDown"); + } - public HtmlTag buildItem(SourcePage page) { - HtmlTag listItem = new HtmlTag("li"); - HtmlTag link = new HtmlTag("a", buildBody(page)); - link.addAttribute("href", buildReference(page)); - link.addAttribute("class", getBooleanPropertiesClasses(page)); - listItem.add(link); - String help = page.getProperty(WikiPageProperty.HELP); - if (!help.isEmpty()) { - if (hasOption("-h", Names.HELP_TOC)) { - listItem.add(HtmlUtil.makeSpanTag("pageHelp", ": " + help)); - } - else if (hasOption("-H", Names.HELP_INSTEAD_OF_TITLE_TOC)) { - link.use(help); - } - else { - link.addAttribute("title", help); - } - } - return listItem; + private int getTotalTestPagesInASuite(SourcePage page) { + if (page.hasProperty(PageType.TEST.toString()) || isSpecialPageToBeCountedAsTest(page)) { + return 1; } - private boolean isSpecialPageToBeCountedAsTest(SourcePage page){ - String pageName = page.getName(); - return pageName.contains("SuiteSetUp") || pageName.contains("SuiteTearDown"); - } - - private int getTotalTestPagesInASuite(SourcePage page) { - if (page.hasProperty(PageType.TEST.toString()) || isSpecialPageToBeCountedAsTest(page)){ - return 1; + int counter = 0; + if (page.hasProperty(PageType.SUITE.toString())) { + Iterator pages = page.getChildren().iterator(); + while (pages.hasNext()) { + SourcePage sourcePage = pages.next(); + counter += getTotalTestPagesInASuite(sourcePage); } - int counter = 0; - if (page.hasProperty(PageType.SUITE.toString())) { - Iterator pages = page.getChildren().iterator(); - while (pages.hasNext()) { - SourcePage sourcePage = pages.next(); - counter += getTotalTestPagesInASuite(sourcePage); - } - } - return counter; + } + return counter; + } + + private String buildBody(SourcePage page) { + String itemText = page.getName(); + //Will show count of test pages under this suite + if (hasOption("-c", Names.TEST_PAGE_COUNT_TOC)) { + if (page.hasProperty(PageType.SUITE.toString())) + itemText += " ( " + getTotalTestPagesInASuite(page) + " )"; } - private String buildBody(SourcePage page) { - String itemText = page.getName(); - //Will show count of test pages under this suite - if (hasOption("-c", Names.TEST_PAGE_COUNT_TOC)) { - if (page.hasProperty(PageType.SUITE.toString())) - itemText += " ( " + getTotalTestPagesInASuite(page) + " )"; - } + if (hasOption("-g", Names.REGRACE_TOC)) { + //todo: DRY? see wikiwordbuilder + itemText = GracefulNamer.regrace(itemText); + } - if (hasOption("-g", Names.REGRACE_TOC)) { - //todo: DRY? see wikiwordbuilder - itemText = GracefulNamer.regrace(itemText); - } + if (hasOption("-p", Names.PROPERTY_TOC)) { + String properties = getBooleanProperties(page); + if (!properties.isEmpty()) itemText += " " + properties; + } - if (hasOption("-p", Names.PROPERTY_TOC)) { - String properties = getBooleanProperties(page); - if (!properties.isEmpty()) itemText += " " + properties; - } + if (hasOption("-f", Names.FILTER_TOC)) { + String filters = page.getProperty(WikiPageProperty.SUITES); + if (!filters.isEmpty()) itemText += " (" + filters + ")"; + } - if (hasOption("-f", Names.FILTER_TOC)) { - String filters = page.getProperty(WikiPageProperty.SUITES); - if (!filters.isEmpty()) itemText += " (" + filters + ")"; - } + return itemText; + } - return itemText; - } + private String buildReference(SourcePage sourcePage) { + return sourcePage.getFullName(); + } - private String buildReference(SourcePage sourcePage) { - return sourcePage.getFullName(); + private int getRecursionLimit() { + String level = contents.findProperty("-R", "0"); + try { + return Integer.parseInt(level); + } catch (NumberFormatException e) { + return 0; } - - private int getRecursionLimit() { - String level = contents.findProperty("-R", "0"); - try { - return Integer.parseInt(level); - } catch (NumberFormatException e) { - return 0; - } + } + + private boolean hasOption(String option, String variableName) { + return contents.hasProperty(option) || + (!variableName.isEmpty() + && contents.findProperty(variableName, "").equals("true")); + } + + private String getBooleanProperties(SourcePage sourcePage) { + String propChars = contents.findProperty(Names.PROPERTY_CHARACTERS, + PROPERTY_CHARACTERS_DEFAULT).trim(); + if (propChars.length() != PROPERTY_CHARACTERS_DEFAULT.length()) { + propChars = PROPERTY_CHARACTERS_DEFAULT; } - private boolean hasOption(String option, String variableName) { - return contents.hasProperty(option) || - (!variableName.isEmpty() - && contents.findProperty(variableName, "").equals("true")); + String result = ""; + if (sourcePage.hasProperty(PageType.SUITE.toString())) result += propChars.charAt(0); + if (sourcePage.hasProperty(PageType.TEST.toString())) result += propChars.charAt(1); + if (sourcePage.hasProperty(WikiImportProperty.PROPERTY_NAME)) result += propChars.charAt(2); + if (page != null && page instanceof WikiSourcePage) { + if (((WikiSourcePage) page).hasSymbolicLinkChild(sourcePage.getName())) result += propChars.charAt(3); } - - private String getBooleanProperties(SourcePage sourcePage) { - String propChars = contents.findProperty(Names.PROPERTY_CHARACTERS, - PROPERTY_CHARACTERS_DEFAULT).trim(); - if(propChars.length() != PROPERTY_CHARACTERS_DEFAULT.length() ){ - propChars = PROPERTY_CHARACTERS_DEFAULT; - } - - String result = ""; - if (sourcePage.hasProperty(PageType.SUITE.toString())) result += propChars.charAt(0); - if (sourcePage.hasProperty(PageType.TEST.toString())) result += propChars.charAt(1); - if (sourcePage.hasProperty(WikiImportProperty.PROPERTY_NAME)) result += propChars.charAt(2); - if (page != null && page instanceof WikiSourcePage){ - if (((WikiSourcePage)page).hasSymbolicLinkChild(sourcePage.getName())) result += propChars.charAt(3); - } - if (sourcePage.hasProperty(WikiPageProperty.PRUNE)) result += propChars.charAt(4); - return result; + if (sourcePage.hasProperty(WikiPageProperty.PRUNE)) result += propChars.charAt(4); + return result; + } + + private String getBooleanPropertiesClasses(SourcePage sourcePage) { + String result = ""; + if (sourcePage.hasProperty(PageType.SUITE.toString())) { + result += "suite"; + } else if (sourcePage.hasProperty(PageType.TEST.toString())) { + result += "test"; + } else { + result += "static"; } - private String getBooleanPropertiesClasses(SourcePage sourcePage) { - String result = ""; - if (sourcePage.hasProperty(PageType.SUITE.toString())) { - result += "suite"; - } - else if (sourcePage.hasProperty(PageType.TEST.toString())) { - result += "test"; - } - else { - result += "static"; + if (sourcePage.hasProperty(WikiImportProperty.PROPERTY_NAME)) result += " linked"; + if (sourcePage.hasProperty(WikiPageProperty.PRUNE)) result += " pruned"; + return result; + } + + private boolean pageContainsTag(SourcePage page, String tagValue) { + if (page.hasProperty("Suites")) { + String suitesProperty = page.getProperty("Suites"); + if (suitesProperty != null && !suitesProperty.isEmpty()) { + Set tagsOnPage = Arrays.stream(suitesProperty.split(",")) + .map(String::trim) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + String[] tagsToMatch = tagValue.split(","); + for (String tag : tagsToMatch) { + if (tagsOnPage.contains(tag.trim().toLowerCase())) { + return true; + } } - if (sourcePage.hasProperty(WikiImportProperty.PROPERTY_NAME)) result += " linked"; - if (sourcePage.hasProperty(WikiPageProperty.PRUNE)) result += " pruned"; - return result; + } } + return false; + } - private static final String PROPERTY_CHARACTERS_DEFAULT = "*+@>-"; + private static final String PROPERTY_CHARACTERS_DEFAULT = "*+@>-"; } From 86d366245ce14d24d8b9403c1ee6e23adf3a6515 Mon Sep 17 00:00:00 2001 From: Jessy Giacomoni Date: Tue, 24 Jun 2025 22:44:14 +0200 Subject: [PATCH 2/2] Take about documentation, include and unit test --- .../ContentsUsage/content.txt | 1 + .../wikitext/shared/ContentsItemBuilder.java | 1 + .../wikitext/parser/ContentsItemTest.java | 37 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/FitNesseRoot/FitNesse/SuiteAcceptanceTests/SuiteWidgetTests/ContentsUsage/content.txt b/FitNesseRoot/FitNesse/SuiteAcceptanceTests/SuiteWidgetTests/ContentsUsage/content.txt index 3504bb72e..6ac710aa9 100644 --- a/FitNesseRoot/FitNesse/SuiteAcceptanceTests/SuiteWidgetTests/ContentsUsage/content.txt +++ b/FitNesseRoot/FitNesse/SuiteAcceptanceTests/SuiteWidgetTests/ContentsUsage/content.txt @@ -15,3 +15,4 @@ Instead of defining arguments on the ''!contents'' widget, variables can be defi -!Defaults: Suite(*), Test(+), Imported(@), Symbolic(>), Skip(-). | PROPERTY_TOC {true} !- -!PROPERTY_CHARACTERS {*+@>-}| |-c |Show number of test pages in a suite. | | +|-tFOO |Only include pages whose 'Suites' property contains FOO.| | diff --git a/src/fitnesse/wikitext/shared/ContentsItemBuilder.java b/src/fitnesse/wikitext/shared/ContentsItemBuilder.java index 35174b439..5560f64b7 100644 --- a/src/fitnesse/wikitext/shared/ContentsItemBuilder.java +++ b/src/fitnesse/wikitext/shared/ContentsItemBuilder.java @@ -9,6 +9,7 @@ import fitnesse.wikitext.SourcePage; import util.GracefulNamer; + import java.util.*; import java.util.stream.Collectors; diff --git a/test/fitnesse/wikitext/parser/ContentsItemTest.java b/test/fitnesse/wikitext/parser/ContentsItemTest.java index 05f51b786..199bfc5e9 100644 --- a/test/fitnesse/wikitext/parser/ContentsItemTest.java +++ b/test/fitnesse/wikitext/parser/ContentsItemTest.java @@ -1,5 +1,6 @@ package fitnesse.wikitext.parser; +import fitnesse.html.HtmlTag; import fitnesse.wikitext.shared.ContentsItemBuilder; import org.junit.Test; @@ -9,6 +10,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; public class ContentsItemTest { @Test @@ -111,6 +113,41 @@ public void assertBuildsSymbolicLinkSuffix() throws Exception{ + HtmlElement.endl + "" + HtmlElement.endl, builder.buildItem(symWikiPage).html()); } + @Test + public void includesPageWhenTagMatches() throws Exception { + Symbol contents = new Symbol(new Contents()); + contents.putProperty("-tag", "important"); + + TestRoot root = new TestRoot(); + WikiPage parent = root.makePage("Parent", "!contents"); + WikiPage taggedChild = parent.addChildPage("TaggedPage"); + PageData childData = taggedChild.getData(); + childData.getProperties().set("Suites", "important"); + taggedChild.commit(childData); + + WikiSourcePage parentSource = new WikiSourcePage(parent); + + ContentsItemBuilder builder = new ContentsItemBuilder(contents, 1, parentSource); + HtmlTag html = builder.buildLevel(parentSource); + + assertTrue(html.html().contains("TaggedPage")); + } + + @Test + public void excludesPageWhenTagDoesNotMatch() throws Exception { + Symbol contents = new Symbol(new Contents()); + contents.putProperty("-tag", "important"); + + WikiPage page = new TestRoot().makePage("OtherPage", "!contents"); + PageData data = page.getData(); + data.getProperties().set("Suites", "notImportant"); + page.commit(data); + + ContentsItemBuilder builder = new ContentsItemBuilder(contents, 1); + HtmlTag html = builder.buildLevel(new WikiSourcePage(page)); + assertFalse(html.html().contains("OtherPage")); + } + private void assertBuildsOption(String page, String[] properties, String option, String variable, String result) throws Exception { assertBuilds(page, properties, option, "", result); assertBuilds(page, properties, "", variable, result);