Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing elements in set of ArgGroup #2350

Closed
oSumAtrIX opened this issue Nov 5, 2024 · 2 comments
Closed

Missing elements in set of ArgGroup #2350

oSumAtrIX opened this issue Nov 5, 2024 · 2 comments
Labels
theme: arg-group An issue or change related to argument groups type: question ❔

Comments

@oSumAtrIX
Copy link

The arguments -e "A" -e "B" -d "C" are parsed into a single Selection element within the selection set, where enabled.selector.name is set to "B" and disabled.selector.name is set to "C". However, I would expect three separate Selection elements: two for the enabled ArgGroup and one for the disabled ArgGroup, according to the following specification:

@ArgGroup(exclusive = false, multiplicity = "0..*")
private var selection = mutableSetOf<Selection>()

internal class Selection {
    @ArgGroup(exclusive = false, multiplicity = "0..*")
    internal var enabled: EnableSelection? = null

    internal class EnableSelection {
        @ArgGroup(multiplicity = "1")
        internal lateinit var selector: EnableSelector

        internal class EnableSelector {
            @CommandLine.Option(
                names = ["-e", "--enable"],
                description = ["Name of the patch."],
                required = true,
            )
            internal var name: String? = null

            @CommandLine.Option(
                names = ["--ei"],
                description = ["Index of the patch in the combined list of the supplied RVP files."],
                required = true,
            )
            internal var index: Int? = null
        }

        @CommandLine.Option(
            names = ["-O", "--options"],
            description = ["Option values keyed by option keys."],
            mapFallbackValue = CommandLine.Option.NULL_VALUE,
            converter = [OptionKeyConverter::class, OptionValueConverter::class],
        )
        internal var options = mutableMapOf<String, Any?>()
    }

    @ArgGroup(exclusive = false, multiplicity = "0..*")
    internal var disable: DisableSelection? = null

    internal class DisableSelection {
        @ArgGroup(multiplicity = "1")
        internal lateinit var selector: DisableSelector

        internal class DisableSelector {
            @CommandLine.Option(
                names = ["-d", "--disable"],
                description = ["Name of the patch."],
                required = true,
            )
            internal var name: String? = null

            @CommandLine.Option(
                names = ["--di"],
                description = ["Index of the patch in the combined list of the supplied RVP files."],
                required = true,
            )
            internal var index: Int? = null
        }
    }
}
@remkop
Copy link
Owner

remkop commented Dec 9, 2024

Have you tried setting multiplicity = "1" on EnableSelection also?

    @ArgGroup(exclusive = false, multiplicity = "1") // not 0..*
    internal var enabled: EnableSelection? = null

@remkop remkop added theme: arg-group An issue or change related to argument groups type: question ❔ labels Dec 9, 2024
@remkop
Copy link
Owner

remkop commented Jan 6, 2025

In the code you show, you have multiplicity="0..*" on variables (Selection.enabled and Selection.disable) which can only take a single value (because their type is not an array nor a Collection).
Therefore, I suggest using multiplicity = "1" in the annotations for these variables.

If Selection.disable is optional, then use multiplicity = "0..1". (You are testing with -e "A" -e "B" -d "C", so I assume that disable is optional.)

After making that change, it seems to work as expected:
The result is 2 Selection objects, and the second of these has a DisableSelection for C.

...
    static class Selection {
        @ArgGroup(exclusive = false, multiplicity = "1" ) // not "0..*"
        EnableSelection enabled = null;

then, my test

        new CommandLine(userObject).parseArgs("-e", "A", "-e", "B", "-d", "C");
        System.out.println(userObject.selection);

prints this:

[Selection{enabled=EnableSelection{selector=EnableSelector{name='A', index=null}, options={}}, disable=null}, 
 Selection{enabled=EnableSelection{selector=EnableSelector{name='B', index=null}, options={}}, disable=DisableSelection{selector=DisableSelector{name='C', index=null}}}]

The result is 2 Selection objects, and the second of these has a DisableSelection for C. I believe this is correct.

The full (java)) code for my test is below (basically I just converted your kotlin to java):

package picocli;

import org.junit.Test;
import picocli.CommandLine.*;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;

public class Issue2350 {
    @ArgGroup(exclusive = false, multiplicity = "0..*")
    private Set<Selection> selection = new LinkedHashSet<Selection>();

    static class Selection {
        @ArgGroup(exclusive = false, multiplicity = "1" ) // can not "0..*" on single-value variable
        EnableSelection enabled = null;

        static class EnableSelection {
            @ArgGroup(multiplicity = "1")
            EnableSelector selector;

            static class EnableSelector {
                @Option(names = {"-e", "--enable"}, description = "Name of the patch.", required = true)
                String name = null;

                @Option(names = {"--ei"}, description = "Index of the patch in the combined list of the supplied RVP files.", required = true)
                Integer index = null;

                @Override
                public String toString() {
                    return "EnableSelector{" + "name='" + name + '\'' + ", index=" + index +  '}';
                }
            }

            @Option(names = {"-O", "--options"}, description = "Option values keyed by option keys.", mapFallbackValue = Option.NULL_VALUE
                //converter = [OptionKeyConverter::class, OptionValueConverter::class]
            )
            Map<String, Object> options = new LinkedHashMap<String, Object>();

            @Override
            public String toString() {
                return "EnableSelection{" + "selector=" + selector + ", options=" + options + '}';
            }
        }

        @ArgGroup(exclusive = false, multiplicity = "0..1") // not 0..* on single-value variable
        DisableSelection disable = null;

        static class DisableSelection {
            @ArgGroup(multiplicity = "1")
            DisableSelector selector;

            static class DisableSelector {
                @Option(names = {"-d", "--disable"}, description = "Name of the patch.", required = true)
                String name = null;

                @Option(names = "--di", description = "Index of the patch in the combined list of the supplied RVP files.", required = true)
                Integer index = null;

                @Override
                public String toString() {
                    return "DisableSelector{" + "name='" + name + '\'' + ", index=" + index + '}';
                }
            }

            @Override
            public String toString() {
                return "DisableSelection{" + "selector=" + selector + '}';
            }
        }

        @Override
        public String toString() {
            return "Selection{" + "enabled=" + enabled + ", disable=" + disable + '}';
        }
    }

    @Test
    public void test1() {
        Issue2350 userObject = new Issue2350();
        new CommandLine(userObject).parseArgs("-e", "A", "-e", "B", "-d", "C");
        System.out.println(userObject.selection);

        assertEquals("selection.size", 2, userObject.selection.size());
        Iterator<Selection> iterator = userObject.selection.iterator();
        Selection selection1 = iterator.next();
        assertEquals("selection1.enabled.selector.name", "A", selection1.enabled.selector.name);
        assertNull("selection1.enabled.selector.index", selection1.enabled.selector.index);
        assertEquals("selection1.enabled.options", Collections.emptyMap(), selection1.enabled.options);
        assertNull("selection1.disable", selection1.disable);

        Selection selection2 = iterator.next();
        assertEquals("selection2.enabled.selector.name", "B", selection2.enabled.selector.name);
        assertNull("selection2.enabled.selector.index", selection2.enabled.selector.index);
        assertEquals("selection2.enabled.options", Collections.emptyMap(), selection2.enabled.options);

        assertNotNull("selection2.disable", selection2.disable);
        assertEquals("selection2.disable.selector.name", "C", selection2.disable.selector.name);
        assertNull("selection2.disable.selector.index", selection2.disable.selector.index);
    }
}

@remkop remkop closed this as completed Jan 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: arg-group An issue or change related to argument groups type: question ❔
Projects
None yet
Development

No branches or pull requests

2 participants