diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt index 00a700fb..a56fcae5 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt @@ -1,7 +1,7 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PreferValidAlternativeCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.deprecatedBoolean import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF @@ -75,8 +75,10 @@ class ConfigParseAddressSectionDadOptionValue : SimpleGrammarOptionValues( "yes means none and no means both. Please use 'both', 'ipv4', 'ipv6' or 'none' instead." // The family names come first: a boolean terminal matches a prefix of the value, so it would - // otherwise take the leading "no" out of "none" and strand "ne". - val DAD = AlternativeCombinator( + // otherwise take the leading "no" out of "none" and strand "ne". PreferValidAlternativeCombinator stops the + // four-character names' lenient shape from taking "fals" out of "false". The family names are a + // case-sensitive table lookup; the boolean (parse_boolean) ignores case. + val DAD = PreferValidAlternativeCombinator( FlexibleLiteralChoiceTerminal("none", "both", "ipv4", "ipv6"), deprecatedBoolean(HISTORICAL), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeTristateOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeTristateOptionValue.kt index 77dcf736..c1b5a674 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeTristateOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeTristateOptionValue.kt @@ -1,20 +1,19 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for CAKE.UseRawPacketSize - * C Function: config_parse_cake_tristate(QDISC_KIND_CAKE) - * Used by Options: CAKE.UseRawPacketSize - * - * Accepts tristate boolean values: yes/no/true/false/on/off/1/0 + * Validator for CAKE.UseRawPacketSize (and AutoRateIngress=, NAT=, Wash=, SplitGSO=) + * C Function: config_parse_cake_tristate(QDISC_KIND_CAKE), src/network/tc/cake.c + * + * Reads the value with parse_tristate() -> parse_tristate_full() -> parse_boolean(), so it is a + * plain boolean in any casing (the empty value is handled by the framework). */ class ConfigParseCakeTristateOptionValue : SimpleGrammarOptionValues( "config_parse_cake_tristate", SequenceCombinator( - FlexibleLiteralChoiceTerminal("1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off"), + BOOLEAN, EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt index 348b2cc2..2ed8abbb 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt @@ -1,21 +1,23 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for VXLAN.IPDoNotFragment - * C Function: config_parse_df(0) - * Used by Options: VXLAN.IPDoNotFragment - * - * Validates the IPv4 Do not Fragment (DF) bit setting. - * Accepts boolean values (yes/no/1/0/true/false/on/off/y/n/t/f) or "inherit". + * C Function: config_parse_df(0), src/network/netdev/vxlan.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(df, ...) over df_table (no, yes, inherit). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseDfOptionValue : SimpleGrammarOptionValues( "config_parse_df", SequenceCombinator( - FlexibleLiteralChoiceTerminal("1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off", "inherit"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("inherit"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt index 013c40bb..ee0b271a 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt @@ -1,21 +1,33 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.DHCP - * C Function: config_parse_dhcp(0) - * Used by Options: Network.DHCP - * - * Enables DHCPv4 and/or DHCPv6 client support. - * Accepts: yes, no, ipv4, or ipv6 + * C Function: config_parse_dhcp(0), src/network/networkd-dhcp-common.c + * + * address_family_from_string first — DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over address_family_table + * (no, yes, ipv4, ipv6) in src/network/networkd-util.c, so any boolean in any casing — then the + * case-sensitive dhcp_deprecated_address_family_table (none, both, v4, v6), which is accepted with a + * "DHCP=%s is deprecated" warning. */ class ConfigParseDhcpOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp", SequenceCombinator( - FlexibleLiteralChoiceTerminal("yes", "no", "ipv4", "ipv6"), + // One names terminal (not two alternatives) so `none` is tried before `no` — the choices are + // matched longest first. + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("ipv4", "ipv6", "none", "both", "v4", "v6").deprecating( + mapOf( + "none" to "DHCP=none is deprecated, please use DHCP=no instead.", + "both" to "DHCP=both is deprecated, please use DHCP=yes instead.", + "v4" to "DHCP=v4 is deprecated, please use DHCP=ipv4 instead.", + "v6" to "DHCP=v6 is deprecated, please use DHCP=ipv6 instead.", + ) + ), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt index 63f44e92..ae77c6c9 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt @@ -1,18 +1,24 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for DHCPServer.PersistLeases - * C Function: config_parse_dhcp_server_persist_leases(0) - * Used by Options: DHCPServer.PersistLeases + * C Function: config_parse_dhcp_server_persist_leases(0), src/network/networkd-dhcp-server.c + * + * DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN over + * dhcp_server_persist_leases_table (no, yes, runtime). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseDhcpServerPersistLeasesOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_server_persist_leases", SequenceCombinator( - FlexibleLiteralChoiceTerminal("runtime", "1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("runtime"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt index bdf2011d..de152848 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt @@ -1,11 +1,11 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PreferValidAlternativeCombinator /** * Validator for Network.DNSSEC (.network) and Resolve.DNSSEC (.conf via resolved.conf). @@ -18,9 +18,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram class ConfigParseDnssecModeOptionValue : SimpleGrammarOptionValues( "config_parse_dnssec_mode", SequenceCombinator( - AlternativeCombinator( - FlexibleLiteralChoiceTerminal("allow-downgrade"), - BOOLEAN + // dnssec_mode_table: the names are case-sensitive, the boolean half (parse_boolean) is not. + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("allow-downgrade"), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt index d3c6f46e..f9ca31ac 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt @@ -1,23 +1,24 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Swap.RuntimeDirectoryPreserve - * C Function: config_parse_exec_preserve_mode(0) - * Used by Options: Swap.RuntimeDirectoryPreserve - * - * Validates the preserve mode for runtime directories: - * - no: directories always removed when service stops - * - restart: directories preserved on automatic and manual restart - * - yes: directories not removed when service stops + * Validator for RuntimeDirectoryPreserve= (Service/Socket/Mount/Swap) + * C Function: config_parse_exec_preserve_mode(0), src/core/load-fragment.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over exec_preserve_mode_table + * (no, yes, restart, on-success) in src/core/execute.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseExecPreserveModeOptionValue : SimpleGrammarOptionValues( "config_parse_exec_preserve_mode", SequenceCombinator( - FlexibleLiteralChoiceTerminal("no", "restart", "yes"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("restart", "on-success"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt index b794f3dc..80120910 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt @@ -1,26 +1,22 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for GENEVE.IPDoNotFragment - * C Function: config_parse_geneve_df(0) - * Used by Options: GENEVE.IPDoNotFragment - * - * Validates the IPDoNotFragment setting for GENEVE tunnels. - * Accepts: "no", "yes", "inherit" and standard boolean values. - * The DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN macro allows boolean aliases - * in addition to the enumerated values. + * C Function: config_parse_geneve_df(0), src/network/netdev/geneve.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(geneve_df, ...) over geneve_df_table (no, yes, inherit). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseGeneveDfOptionValue : SimpleGrammarOptionValues( "config_parse_geneve_df", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "no", "yes", "inherit", - "1", "y", "true", "t", "on", - "0", "n", "false", "f", "off" + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("inherit"), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt index ba42b26a..ba26b18b 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt @@ -1,30 +1,30 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.IPMasquerade - * C Function: config_parse_ip_masquerade(0) - * Used by Options: Network.IPMasquerade - * - * Accepts: ipv4, ipv6, both, no, and deprecated boolean values - * (yes/true/on/1 map to ipv4, no/false/off/0 map to no) + * C Function: config_parse_ip_masquerade(0), src/network/networkd-util.c + * + * Calls parse_boolean() first (case-insensitive; yes/true/on/1 map to ipv4, no/false/off/0 to no), + * then ip_masquerade_address_family_from_string, a plain case-sensitive lookup over + * ip_masquerade_address_family_table (no, both, ipv4, ipv6). */ class ConfigParseIpMasqueradeOptionValue : SimpleGrammarOptionValues( "config_parse_ip_masquerade", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - // Primary values - "ipv4", "ipv6", "both", "no", - // Deprecated boolean true values (map to ipv4) - "1", "yes", "y", "true", "t", "on", - // Deprecated boolean false values (map to no) - "0", "n", "false", "f", "off" - ).deprecating( - // systemd.network(5): the boolean forms "are now deprecated. Please use one of the - // values above" (ipv4/ipv6/both/no). Still accepted; the truthy forms map to ipv4. + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("ipv4", "ipv6", "both"), + DEPRECATED_BOOLEAN + ), + EOF() + ) +) { + private companion object { + // systemd.network(5): the boolean forms "are now deprecated. Please use one of the values + // above" (ipv4/ipv6/both/no). Still accepted; the truthy forms map to ipv4. + val DEPRECATED_BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true).deprecating( buildMap { for (t in listOf("1", "yes", "y", "true", "t", "on")) { put(t, "Boolean values for IPMasquerade= are deprecated; use \"ipv4\" (or \"ipv6\"/\"both\") instead.") @@ -33,7 +33,6 @@ class ConfigParseIpMasqueradeOptionValue : SimpleGrammarOptionValues( put(f, "Boolean values for IPMasquerade= are deprecated; use \"no\" instead.") } } - ), - EOF() - ) -) + ) + } +} diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt index 18b42d96..958efb6b 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt @@ -1,26 +1,23 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.IPv6PrivacyExtensions - * C Function: config_parse_ipv6_privacy_extensions(0) - * Used by Options: Network.IPv6PrivacyExtensions - * - * Accepts boolean values (yes/no/true/false/on/off/1/0/y/n/t/f) or the special values - * "prefer-public" and "kernel". + * C Function: config_parse_ipv6_privacy_extensions(0), src/network/networkd-sysctl.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over ipv6_privacy_extensions_table + * (no, prefer-public, yes, kernel). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseIpv6PrivacyExtensionsOptionValue : SimpleGrammarOptionValues( "config_parse_ipv6_privacy_extensions", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - // Boolean values - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - // Special values - "prefer-public", "kernel" + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("prefer-public", "kernel"), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt index 8a17ee71..b2051104 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt @@ -1,30 +1,24 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.KeepConfiguration - * C Function: config_parse_keep_configuration(0) - * Used by Options: Network.KeepConfiguration - * - * Accepts: - * - Boolean values: 1, yes, y, true, t, on, 0, no, n, false, f, off - * - Specific values: static, dynamic-on-stop, dynamic - * - Backward compatibility: dhcp, dhcp-on-stop + * C Function: config_parse_keep_configuration(0), src/network/networkd-network.c + * + * First the backward-compatibility names `dhcp` and `dhcp-on-stop` (streq, case-sensitive), then + * keep_configuration_from_string: DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over + * keep_configuration_table (no, dynamic-on-stop, dynamic, static, yes). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseKeepConfigurationOptionValue : SimpleGrammarOptionValues( "config_parse_keep_configuration", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - // Boolean values (map to yes/no) - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - // Explicit enum values - "static", "dynamic-on-stop", "dynamic", - // Backward compatibility - "dhcp", "dhcp-on-stop" + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("dynamic-on-stop", "dynamic", "static", "dhcp", "dhcp-on-stop"), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt index 5d560510..bfdb6b82 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt @@ -1,21 +1,24 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.LinkLocalAddressing - * C Function: config_parse_link_local_address_family(0) - * Used by Options: Network.LinkLocalAddressing + * C Function: config_parse_link_local_address_family(0), src/network/networkd-util.c + * + * link_local_address_family_from_string: the compat names `fallback` and `fallback-ipv4` (streq), + * then address_family_from_string, a DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over + * address_family_table (no, yes, ipv4, ipv6). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseLinkLocalAddressFamilyOptionValue : SimpleGrammarOptionValues( "config_parse_link_local_address_family", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "yes", "no", "true", "false", "1", "0", "on", "off", "y", "n", "t", "f", - "ipv4", "ipv6", - "fallback", "fallback-ipv4" + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("ipv4", "ipv6", "fallback", "fallback-ipv4"), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt index bdc4ba5c..0af85e15 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt @@ -1,23 +1,22 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.LLDP - * C Function: config_parse_lldp_mode(0) - * Used by Options: Network.LLDP - * - * Accepts boolean values (yes/no/true/false/on/off/1/0) or the special value "routers-only". + * C Function: config_parse_lldp_mode(0), src/network/networkd-lldp-rx.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over lldp_mode_table (no, yes, routers-only). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseLldpModeOptionValue : SimpleGrammarOptionValues( "config_parse_lldp_mode", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "yes", "y", "true", "t", "on", "1", - "no", "n", "false", "f", "off", "0", - "routers-only" + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("routers-only"), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt index 13cfdccd..fbfdf663 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt @@ -1,20 +1,24 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Swap.MemoryPressureWatch - * C Function: config_parse_pressure_watch(0) - * Used by Options: Swap.MemoryPressureWatch + * C Function: config_parse_pressure_watch(0), src/core/load-fragment.c * - * Accepts boolean values (yes/no/1/0/true/false/on/off/y/n/t/f) or special values (auto/skip). + * cgroup_pressure_watch_from_string: DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over + * cgroup_pressure_watch_table (no, yes, auto, skip) in src/core/cgroup.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseMemoryPressureWatchOptionValue : SimpleGrammarOptionValues( "config_parse_pressure_watch", SequenceCombinator( - FlexibleLiteralChoiceTerminal("yes", "no", "1", "0", "true", "false", "on", "off", "y", "n", "t", "f", "auto", "skip"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("auto", "skip"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt index 3a9a8901..b83781b2 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt @@ -1,18 +1,25 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Bridge.MulticastRouter - * C Function: config_parse_multicast_router(0) - * Used by Options: Bridge.MulticastRouter + * C Function: config_parse_multicast_router(0), src/network/netdev/bridge.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(multicast_router, MulticastRouter, _MULTICAST_ROUTER_INVALID) + * over multicast_router_table (no, query, permanent, temporary). A *true* boolean maps to + * _MULTICAST_ROUTER_INVALID and is rejected, so only the false half of the booleans is accepted. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseMulticastRouterOptionValue : SimpleGrammarOptionValues( "config_parse_multicast_router", SequenceCombinator( - FlexibleLiteralChoiceTerminal("no", "query", "permanent", "temporary"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("query", "permanent", "temporary"), + BOOLEAN_FALSE, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNamespaceFlagsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNamespaceFlagsOptionValue.kt index f0fc9cca..9ae10b52 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNamespaceFlagsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNamespaceFlagsOptionValue.kt @@ -1,11 +1,11 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PreferValidAlternativeCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore @@ -42,7 +42,9 @@ private val NAMESPACE_LIST = SequenceCombinator( class ConfigParseNamespaceFlagsOptionValue : SimpleGrammarOptionValues( "config_parse_namespace_flags", SequenceCombinator( - AlternativeCombinator( + // PreferValid: NAMESPACE_TYPE's lenient lower-case shape would otherwise take the `t` out of a + // mixed-case boolean like `tRUE` (parse_boolean ignores case) and strand the rest. + PreferValidAlternativeCombinator( NAMESPACE_LIST, BOOLEAN ), diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt index 5533679d..2d8c8d76 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt @@ -1,20 +1,24 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for IPv6AcceptRA.DHCPv6Client - * C Function: config_parse_ndisc_start_dhcp6_client(0) - * Used by Options: IPv6AcceptRA.DHCPv6Client - * - * Accepts boolean values (yes/no/true/false/on/off/1/0/y/n/t/f) or the special value "always". + * C Function: config_parse_ndisc_start_dhcp6_client(0), src/network/networkd-ndisc.c + * + * DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN over ndisc_start_dhcp6_client_table + * (no, always, yes). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseNdiscStartDhcp6ClientOptionValue : SimpleGrammarOptionValues( "config_parse_ndisc_start_dhcp6_client", SequenceCombinator( - FlexibleLiteralChoiceTerminal("always", "yes", "no", "true", "false", "on", "off", "1", "0", "y", "n", "t", "f"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("always"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt index f172964e..1f5748bd 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt @@ -1,23 +1,26 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Swap.PrivateTmp - * C Function: config_parse_private_tmp(0) - * Used by Options: Swap.PrivateTmp - * - * Takes a boolean argument, or disconnected. Valid values are: - * - Boolean true: 1, yes, y, true, t, on - * - Boolean false: 0, no, n, false, f, off - * - disconnected + * Validator for PrivateTmp= (Service/Socket/Mount/Swap) + * C Function: config_parse_private_tmp(0), src/core/load-fragment.c + * + * private_tmp_from_string: DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over private_tmp_table + * (no, connected, disconnected) in src/core/namespace.c. `connected` is what a true boolean maps to + * and is accepted by the lookup even though the man page only documents booleans and `disconnected`. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case: + * `PrivateTmp=True` is valid, `PrivateTmp=Disconnected` is not. */ class ConfigParsePrivateTmpOptionValue : SimpleGrammarOptionValues( "config_parse_private_tmp", SequenceCombinator( - FlexibleLiteralChoiceTerminal("1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off", "disconnected"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("connected", "disconnected"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt index 375bede2..5e45570b 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt @@ -1,23 +1,23 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Swap.ProtectControlGroups - * C Function: config_parse_protect_control_groups(0) - * Used by Options: Swap.ProtectControlGroups - * - * Accepts boolean values (true/false/yes/no/1/0/on/off/y/n/t/f) or special values "private" and "strict". + * Validator for ProtectControlGroups= (Service/Socket/Mount/Swap) + * C Function: config_parse_protect_control_groups(0), src/core/load-fragment.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over protect_control_groups_table + * (no, yes, private, strict) in src/core/namespace.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseProtectControlGroupsOptionValue : SimpleGrammarOptionValues( "config_parse_protect_control_groups", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - "private", "strict" + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("private", "strict"), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt index 9ef92965..ef313de8 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt @@ -1,27 +1,23 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Swap.ProtectHome - * C Function: config_parse_protect_home(0) - * Used by Options: Swap.ProtectHome - * - * Validates values for ProtectHome= option which accepts: - * - Boolean values (yes, no, true, false, on, off, 1, 0, y, n, t, f) - * - Special values: read-only, tmpfs + * Validator for ProtectHome= (Service/Socket/Mount/Swap) + * C Function: config_parse_protect_home(0), src/core/load-fragment.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over protect_home_table + * (no, yes, read-only, tmpfs) in src/core/namespace.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseProtectHomeOptionValue : SimpleGrammarOptionValues( "config_parse_protect_home", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - // Boolean values - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - // Special values - "read-only", "tmpfs" + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("read-only", "tmpfs"), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt index 9534edf0..81dfad74 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt @@ -5,15 +5,17 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram /** * Validator for Service.ProtectHostname (also Socket/Mount/Swap.ProtectHostname). - * C Function: config_parse_protect_hostname(0) + * C Function: config_parse_protect_hostname(0), src/core/load-fragment.c * * Accepts either: - * - a boolean (yes/no/y/n/true/false/t/f/1/0/on/off) or "private" - * - ":" where is a "true" boolean (yes/y/true/t/1/on) - * or "private", and matches hostname_is_valid (LDH chars; labels - * joined by '.' with no leading/trailing hyphens). + * - a boolean or "private" + * - ":" where is a "true" boolean or "private", and + * matches hostname_is_valid (LDH chars; labels joined by '.' with no leading/trailing hyphens). * - * The C code rejects "no:" (and other false synonyms with a hostname). + * The keyword goes through protect_hostname_from_string, a DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over + * protect_hostname_table (no, yes, private) in src/core/namespace.c: the boolean half ignores case + * (parse_boolean), the names do not. The C code rejects "no:" (and other false synonyms + * with a hostname). */ class ConfigParseProtectHostnameOptionValue : SimpleGrammarOptionValues( "config_parse_protect_hostname", @@ -21,31 +23,24 @@ class ConfigParseProtectHostnameOptionValue : SimpleGrammarOptionValues( AlternativeCombinator( // Form: : SequenceCombinator( - BOOLEAN_TRUE_OR_PRIVATE, + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("private"), + BOOLEAN_TRUE, + ), COLON_LITERAL, HOSTNAME ), // Form: standalone keyword (any boolean or "private") - BOOLEAN_OR_PRIVATE + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("private"), + BOOLEAN, + ) ), EOF() ) ) { companion object { private val COLON_LITERAL = LiteralChoiceTerminal(":") - - // Boolean true synonyms plus "private" (used when a hostname is supplied). - private val BOOLEAN_TRUE_OR_PRIVATE = FlexibleLiteralChoiceTerminal( - "yes", "y", "true", "t", "on", "1", "private" - ) - - // Any boolean (true or false synonyms) plus "private". - private val BOOLEAN_OR_PRIVATE = FlexibleLiteralChoiceTerminal( - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - "private" - ) - // Hostname: one or more LDH labels separated by '.'. Each label starts and // ends with [A-Za-z0-9] and may contain hyphens internally. private val HOSTNAME = RegexTerminal( diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt index c93a2d10..d4372f66 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt @@ -1,23 +1,23 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Swap.ProtectSystem - * C Function: config_parse_protect_system(0) - * Used by Options: Swap.ProtectSystem - * - * Accepts boolean values or the special values "full" or "strict". + * Validator for ProtectSystem= (Service/Socket/Mount/Swap) + * C Function: config_parse_protect_system(0), src/core/load-fragment.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over protect_system_table + * (no, yes, full, strict) in src/core/namespace.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseProtectSystemOptionValue : SimpleGrammarOptionValues( "config_parse_protect_system", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - "full", "strict" + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("full", "strict"), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt index 07153f4c..11a7565d 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt @@ -1,18 +1,24 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Socket.DeferTrigger - * C Function: config_parse_socket_defer_trigger(0) - * Used by Options: Socket.DeferTrigger + * C Function: config_parse_socket_defer_trigger(0), src/core/load-fragment.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over socket_defer_trigger_table + * (no, yes, patient) in src/core/socket.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseSocketDeferTriggerOptionValue : SimpleGrammarOptionValues( "config_parse_socket_defer_trigger", SequenceCombinator( - FlexibleLiteralChoiceTerminal("patient", "yes", "y", "true", "t", "on", "1", "no", "n", "false", "f", "off", "0"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("patient"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt index f2db33b4..07227c01 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt @@ -1,18 +1,24 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Exec.Timezone - * C Function: config_parse_timezone_mode(0) - * Used by Options: Exec.Timezone + * Validator for Exec.Timezone (.nspawn) + * C Function: config_parse_timezone_mode(0), src/nspawn/nspawn-settings.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over timezone_mode_table + * (off, copy, bind, symlink, delete, auto). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseTimezoneModeOptionValue : SimpleGrammarOptionValues( "config_parse_timezone_mode", SequenceCombinator( - FlexibleLiteralChoiceTerminal("off", "copy", "bind"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("copy", "bind", "symlink", "delete", "auto"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt index 4c8bb05a..0e7e9691 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt @@ -1,20 +1,23 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Network.UseDomains - * C Function: config_parse_use_domains(0) - * Used by Options: Network.UseDomains - * - * Accepts boolean values (yes/no/true/false/1/0/on/off/y/n/t/f) or the special value "route". + * Validator for Network.UseDomains (and the per-protocol UseDomains= keys) + * C Function: config_parse_use_domains(0), src/network/networkd-dns.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over use_domains_table (no, route, yes). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseUseDomainsOptionValue : SimpleGrammarOptionValues( "config_parse_use_domains", SequenceCombinator( - FlexibleLiteralChoiceTerminal("yes", "no", "true", "false", "1", "0", "on", "off", "y", "n", "t", "f", "route"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("route"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt index 4294edfa..5adb02da 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt @@ -1,40 +1,23 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Files.PrivateUsersOwnership - * C Function: config_parse_userns_ownership(0) - * Used by Options: Files.PrivateUsersOwnership - * - * Validates user namespace ownership modes: - * - off: Disable ownership adjustment - * - chown: Change ownership of files - * - map: Map ownership via user namespaces - * - * Also accepts boolean values (yes/no/true/false/on/off/1/0) due to - * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN macro. + * Validator for Files.PrivateUsersOwnership (.nspawn) + * C Function: config_parse_userns_ownership(0), src/nspawn/nspawn-settings.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over user_namespace_ownership_table + * (off, chown, map, foreign, auto). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseUsernsOwnershipOptionValue : SimpleGrammarOptionValues( "config_parse_userns_ownership", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "off", - "chown", - "map", - "yes", - "no", - "true", - "false", - "on", - "1", - "0", - "y", - "n", - "t", - "f" + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("chown", "map", "foreign", "auto"), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt index 0bf52999..97c605e1 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt @@ -26,6 +26,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp /** * The spellings parse_boolean() accepts (src/basic/parse-util.c). Kept as an array so a caller that * needs its *own* boolean terminal can build one without reusing [BOOLEAN] — see [deprecatedBoolean]. + * + * parse_boolean() compares with STRCASE_IN_SET, so any casing is accepted (`Yes`, `TRUE`, `oFf`). + * Every terminal built from these spellings must therefore pass `ignoreCase = true`; the lower-case + * forms here stay the canonical spelling that quick-fixes suggest. */ val BOOLEAN_SPELLINGS = arrayOf("1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off") @@ -41,12 +45,17 @@ val BOOLEAN_SPELLINGS = arrayOf("1", "yes", "y", "true", "t", "on", "0", "no", " * Watch the ordering when you do: this terminal matches a *prefix* of the value, so on * `ConditionVirtualization=none` it would happily match the leading `no` and strand `ne`. Under the * classic engine AlternativeCombinator commits to the first branch that matches and never backtracks, - * so the names have to come first. + * so the names have to come first. Because this terminal ignores case it can prefix-match more than + * a lower-case one would (`No` inside `None`), and a lower-case names terminal placed before it can in + * turn strand a mixed-case boolean (`t` out of `tRUE`). Write such settings as + * `PreferValidAlternativeCombinator(FlexibleLiteralChoiceTerminal(), BOOLEAN)`: names first, and + * leave the boolean spellings (`yes`, `no`, `off`, ...) out of the names, since this terminal already + * accepts them in any case. * * Do NOT call [FlexibleLiteralChoiceTerminal.deprecating] on this instance — it mutates in place and * this one is shared across every validator. */ -val BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS) +val BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true) /** * Only the *false* half of [BOOLEAN_SPELLINGS]. @@ -56,7 +65,15 @@ val BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS) * example: `PreferredSource=no` forbids a DHCP-supplied source, while `PreferredSource=yes` is simply * not an address. */ -val BOOLEAN_FALSE = FlexibleLiteralChoiceTerminal("0", "no", "n", "false", "f", "off") +val BOOLEAN_FALSE = FlexibleLiteralChoiceTerminal("0", "no", "n", "false", "f", "off", ignoreCase = true) + +/** + * Only the *true* half of [BOOLEAN_SPELLINGS]. + * + * For settings that run parse_boolean() but reject a false result in some form — + * config_parse_protect_hostname accepts `yes:HOSTNAME` but not `no:HOSTNAME`. + */ +val BOOLEAN_TRUE = FlexibleLiteralChoiceTerminal("1", "yes", "y", "true", "t", "on", ignoreCase = true) /** * A fresh boolean terminal with every spelling marked deprecated for [reason]. @@ -66,7 +83,8 @@ val BOOLEAN_FALSE = FlexibleLiteralChoiceTerminal("0", "no", "n", "false", "f", * the shared [BOOLEAN] must not be poisoned. */ fun deprecatedBoolean(reason: String): FlexibleLiteralChoiceTerminal = - FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS).deprecating(BOOLEAN_SPELLINGS.associateWith { reason }) + FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true).deprecating(BOOLEAN_SPELLINGS.associateWith { reason }) + val BYTES = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*") val DEVICE = RegexTerminal("\\S+\\s*", "/[^\\u0000. ]+\\s*") val IOPS = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*") @@ -460,9 +478,10 @@ val CONDITION_ARCHITECTURE = FlexibleLiteralChoiceTerminal( /** * A virtualization parameter — condition_test_virtualization. Accepts, in order: `private-users`, a * boolean, the categories `vm`/`container`, or any id in virtualization_table (src/basic/virt.c). - * [BOOLEAN] must follow the names so it cannot strand the `no` inside `none`. + * [BOOLEAN] must follow the names so it cannot strand the `no` inside `none`. The boolean ignores case + * (parse_boolean) but the names do not (virtualization_from_string is a plain string-table lookup). */ -val CONDITION_VIRTUALIZATION = AlternativeCombinator( +val CONDITION_VIRTUALIZATION = PreferValidAlternativeCombinator( FlexibleLiteralChoiceTerminal( "vm", "container", "private-users", "none", "kvm", "amazon", "qemu", "bochs", "xen", "uml", "vmware", "oracle", "microsoft", "zvm", diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt new file mode 100644 index 00000000..939cd3c0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt @@ -0,0 +1,36 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar + +/** + * A workaround for the LEGACY (classic SyntacticMatch/SemanticMatch) engine only. Under the new + * list-of-successes engine ([parse]/validate) it behaves exactly like [AlternativeCombinator]. + * + * The classic engine's [AlternativeCombinator] returns the first branch that *syntactically* matches + * and never backtracks. A [FlexibleLiteralChoiceTerminal] matches syntactically with a lenient regex + * built from its choices' characters and capped at the longest choice, so a first branch can grab + * part of a value that belongs to a later branch, and the enclosing sequence then fails on the + * stranded rest. Every one of these is valid, and every one fails with a plain AlternativeCombinator: + * + * - `Alt(Flex("yes", "no"), Flex("maybe"))` on `maybe`: the first regex `[a-z]{1,3}` takes `may`. + * - `Alt(Flex("ab"), Flex("abc"))` on `abc`: the exact prefix `ab` wins before `abc` is tried. + * - `Alt(Flex("none", "both", "ipv4", "ipv6"), BOOLEAN)` on `false`: the 4-char cap takes `fals`. + * - `Alt(Flex("connected", "disconnected"), BOOLEAN)` on `tRUE`: the lower-case class stops at `R`. + * + * This class first asks each branch for an exact (semantic) match and returns the first that has + * one; only if none does it fall back to the usual lenient first-match, so an invalid value can + * still be located and offered quick-fixes. Order still matters for that exact pass: put longer or + * more specific branches first (e.g. names before [BOOLEAN], so `no` cannot be taken out of `none`). + * + * Can be replaced by a plain [AlternativeCombinator] once the legacy engine is removed. + */ +class PreferValidAlternativeCombinator(vararg tokens: Combinator) : AlternativeCombinator(*tokens) { + + override fun SyntacticMatch(value: String, offset: Int): MatchResult { + for (token in tokens) { + val match = token.SemanticMatch(value, offset) + if (match.matchResult != -1) { + return match + } + } + return super.SyntacticMatch(value, offset) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/CaseInsensitiveBooleanInspectionTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/CaseInsensitiveBooleanInspectionTest.kt new file mode 100644 index 00000000..df13cb1d --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/CaseInsensitiveBooleanInspectionTest.kt @@ -0,0 +1,185 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import org.junit.Test + +/** + * systemd's parse_boolean() (src/basic/parse-util.c) compares with STRCASE_IN_SET, so `Yes`, `TRUE` + * and `oFf` are as valid as `yes`. Settings that take "a boolean or one of these names" go through + * string_table_lookup_from_string_with_boolean (src/basic/string-table.c), which tries parse_boolean() + * first and then a *case-sensitive* streq() over the table: only the boolean half ignores case. + * + * Expectations are derived from the C at 0f0682b773. Each validator is exercised under whichever + * grammar engine the test run selects (`-Dsystemd.unit.grammarParseEngine=true` forces the new one). + */ +class CaseInsensitiveBooleanInspectionTest : AbstractUnitFileTest() { + + private fun highlights(fileName: String, text: String): Int { + setupFileInEditor(fileName, text) + enableInspection(InvalidValueInspection::class.java) + return myFixture.doHighlighting().size + } + + private fun assertAccepted(fileName: String, vararg lines: String) = + assertEquals(lines.joinToString(), 0, highlights(fileName, lines.joinToString("\n") + "\n")) + + /** Each of [values] for [key] in [section] is rejected on its own. */ + private fun assertEachRejected(fileName: String, section: String, key: String, vararg values: String) { + for (value in values) { + val text = "$section\n$key=$value\n" + assertEquals(text, 1, highlights(fileName, text)) + } + } + + @Test + fun testPlainBooleanAcceptsAnyCasing() { + assertAccepted( + "f.service", + "[Service]", + "PrivatePIDs=Yes", + "PrivatePIDs=TRUE", + "PrivatePIDs=oFf", + "PrivatePIDs=N", + "PrivatePIDs=T", + "PrivatePIDs=On", + ) + assertEachRejected("f.service", "[Service]", "PrivatePIDs", "yess", "Truee", "2") + } + + @Test + fun testPrivateTmpBooleanHalfIgnoresCaseButNamesDoNot() { + assertAccepted( + "f.service", + "[Service]", + "PrivateTmp=True", + "PrivateTmp=FALSE", + "PrivateTmp=Yes", + "PrivateTmp=oFf", + "PrivateTmp=tRUE", + "PrivateTmp=disconnected", + "PrivateTmp=connected", + ) + assertEachRejected("f.service", "[Service]", "PrivateTmp", "Disconnected", "DISCONNECTED", "Connected", "yess") + } + + @Test + fun testPrivateUsersAcceptsMixedCaseBoolean() { + assertAccepted("f.service", "[Service]", "PrivateUsers=False", "PrivateUsers=TRUE", "PrivateUsers=oFf") + assertAccepted("f.nspawn", "[Exec]", "PrivateUsers=False", "PrivateUsers=Yes", "PrivateUsers=pick") + assertEachRejected("f.nspawn", "[Exec]", "PrivateUsers", "Pick", "Identity", "yess") + } + + @Test + fun testProtectSettings() { + assertAccepted( + "f.service", + "[Service]", + "ProtectSystem=Yes", + "ProtectSystem=strict", + "ProtectHome=TRUE", + "ProtectHome=read-only", + "ProtectControlGroups=On", + "ProtectControlGroups=private", + "ProtectHostname=Yes", + "ProtectHostname=fALSE", + "ProtectHostname=private", + "ProtectHostname=tRUE:myhost", + "ProtectHostname=Yes:my-host.example", + "RuntimeDirectoryPreserve=Yes", + "RuntimeDirectoryPreserve=on-success", + "MemoryPressureWatch=On", + "MemoryPressureWatch=skip", + "RestrictNamespaces=tRUE", + "RestrictNamespaces=~net ipc", + "Delegate=Yes", + "Delegate=cpu io", + ) + assertEachRejected("f.service", "[Service]", "ProtectSystem", "Strict", "FULL") + assertEachRejected("f.service", "[Service]", "ProtectHome", "Read-Only", "Tmpfs") + assertEachRejected("f.service", "[Service]", "ProtectControlGroups", "Private") + // A false boolean with a hostname is refused (t == PROTECT_HOSTNAME_NO && h), in any casing. + assertEachRejected("f.service", "[Service]", "ProtectHostname", "Private", "No:myhost", "fALSE:myhost") + assertEachRejected("f.service", "[Service]", "RuntimeDirectoryPreserve", "Restart") + assertEachRejected("f.service", "[Service]", "MemoryPressureWatch", "Auto") + assertEachRejected("f.service", "[Service]", "RestrictNamespaces", "NET") + } + + @Test + fun testSocketDeferTrigger() { + assertAccepted("f.socket", "[Socket]", "DeferTrigger=TRUE", "DeferTrigger=patient") + assertEachRejected("f.socket", "[Socket]", "DeferTrigger", "Patient") + } + + @Test + fun testConditionVirtualization() { + assertAccepted( + "f.service", + "[Unit]", + "ConditionVirtualization=No", + "ConditionVirtualization=!Yes", + "ConditionVirtualization=|tRUE", + "ConditionVirtualization=none", + ) + } + + @Test + fun testNetworkSettings() { + assertAccepted( + "f.network", + "[Network]", + "DHCP=True", + "DHCP=ipv4", + "LinkLocalAddressing=Yes", + "LinkLocalAddressing=fallback", + "IPv6PrivacyExtensions=On", + "IPv6PrivacyExtensions=prefer-public", + "LLDP=TRUE", + "LLDP=routers-only", + "KeepConfiguration=Yes", + "KeepConfiguration=dhcp-on-stop", + "UseDomains=Yes", + "UseDomains=route", + "DNSSEC=Yes", + "DNSSEC=allow-downgrade", + "IPMasquerade=both", + "IPMasquerade=No", + ) + assertEachRejected("f.network", "[Network]", "DHCP", "IPv4", "yess") + assertEachRejected("f.network", "[Network]", "LLDP", "Routers-Only") + assertEachRejected("f.network", "[Network]", "KeepConfiguration", "Static") + assertEachRejected("f.network", "[Network]", "DNSSEC", "Allow-Downgrade") + assertEachRejected("f.network", "[Network]", "IPMasquerade", "IPv4", "Both") + + assertAccepted("f.network", "[Address]", "DuplicateAddressDetection=False", "DuplicateAddressDetection=none") + assertEachRejected("f.network", "[Address]", "DuplicateAddressDetection", "None", "IPv6") + + assertAccepted("f.network", "[DHCPServer]", "PersistLeases=Yes", "PersistLeases=runtime") + assertEachRejected("f.network", "[DHCPServer]", "PersistLeases", "Runtime") + + assertAccepted("f.network", "[IPv6AcceptRA]", "DHCPv6Client=Yes", "DHCPv6Client=always") + assertEachRejected("f.network", "[IPv6AcceptRA]", "DHCPv6Client", "Always") + } + + @Test + fun testNetdevSettings() { + assertAccepted("f.netdev", "[VXLAN]", "IPDoNotFragment=Yes", "IPDoNotFragment=inherit") + assertEachRejected("f.netdev", "[VXLAN]", "IPDoNotFragment", "Inherit") + + assertAccepted("f.netdev", "[GENEVE]", "IPDoNotFragment=No", "IPDoNotFragment=inherit") + assertEachRejected("f.netdev", "[GENEVE]", "IPDoNotFragment", "INHERIT") + + // [Bridge] MulticastRouter= lives in .network files. multicast_router's WITH_BOOLEAN "yes" value is + // _MULTICAST_ROUTER_INVALID, so only false booleans are accepted. + assertAccepted("f.network", "[Bridge]", "MulticastRouter=No", "MulticastRouter=FALSE", "MulticastRouter=query") + assertEachRejected("f.network", "[Bridge]", "MulticastRouter", "Yes", "true", "Query") + } + + @Test + fun testNspawnSettings() { + assertAccepted("f.nspawn", "[Exec]", "Timezone=Off", "Timezone=TRUE", "Timezone=symlink") + assertEachRejected("f.nspawn", "[Exec]", "Timezone", "Copy", "Auto") + + assertAccepted("f.nspawn", "[Files]", "PrivateUsersOwnership=Yes", "PrivateUsersOwnership=chown") + assertEachRejected("f.nspawn", "[Files]", "PrivateUsersOwnership", "Chown", "Map") + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionValueTest.kt index 52b87fb9..d6347ae6 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionValueTest.kt @@ -78,8 +78,10 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { assertSize(0, highlights) } + // config_parse_dhcp falls back to dhcp_deprecated_address_family_table (none/both/v4/v6) with a + // deprecation warning rather than rejecting the value. @Test - fun testInvalidValueBoth() { + fun testDeprecatedValueBothIsAccepted() { // Fixture Setup // language="unit file (systemd)" val file = """ @@ -93,11 +95,12 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { val highlights = myFixture.doHighlighting() // Verification - assertSize(1, highlights) + assertSize(0, highlights) } + // address_family_from_string is DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN, so booleans are accepted. @Test - fun testInvalidValueTrue() { + fun testBooleanTrueIsAccepted() { // Fixture Setup // language="unit file (systemd)" val file = """ @@ -111,11 +114,11 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { val highlights = myFixture.doHighlighting() // Verification - assertSize(1, highlights) + assertSize(0, highlights) } @Test - fun testInvalidValueV4() { + fun testDeprecatedValueV4IsAccepted() { // Fixture Setup // language="unit file (systemd)" val file = """ @@ -129,7 +132,7 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { val highlights = myFixture.doHighlighting() // Verification - assertSize(1, highlights) + assertSize(0, highlights) } @Test @@ -142,6 +145,9 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { DHCP=no DHCP=ipv4 DHCP=ipv6 + DHCP=True + DHCP=oFf + DHCP=1 """.trimIndent() // Execute SUT @@ -161,7 +167,7 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { [Network] DHCP=enabled DHCP=all - DHCP=v6 + DHCP=IPv4 """.trimIndent() // Execute SUT diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseExecPreserveModeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseExecPreserveModeOptionValueTest.kt index bce896c3..3ba494b0 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseExecPreserveModeOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseExecPreserveModeOptionValueTest.kt @@ -15,6 +15,11 @@ class ConfigParseExecPreserveModeOptionValueTest : AbstractUnitFileTest() { RuntimeDirectoryPreserve=no RuntimeDirectoryPreserve=restart RuntimeDirectoryPreserve=yes + RuntimeDirectoryPreserve=on-success + RuntimeDirectoryPreserve=true + RuntimeDirectoryPreserve=false + RuntimeDirectoryPreserve=Yes + RuntimeDirectoryPreserve=OFF """.trimIndent() // Execute SUT @@ -33,8 +38,8 @@ class ConfigParseExecPreserveModeOptionValueTest : AbstractUnitFileTest() { val file = """ [Swap] RuntimeDirectoryPreserve=invalid - RuntimeDirectoryPreserve=true - RuntimeDirectoryPreserve=false + RuntimeDirectoryPreserve=Restart + RuntimeDirectoryPreserve=yess RuntimeDirectoryPreserve=always """.trimIndent() diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTimezoneModeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTimezoneModeOptionValueTest.kt index 20002bea..0b5d0816 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTimezoneModeOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTimezoneModeOptionValueTest.kt @@ -15,6 +15,12 @@ class ConfigParseTimezoneModeOptionValueTest : AbstractUnitFileTest() { Timezone=off Timezone=copy Timezone=bind + Timezone=symlink + Timezone=delete + Timezone=auto + Timezone=on + Timezone=TRUE + Timezone=No """.trimIndent() // Execute SUT @@ -33,9 +39,9 @@ class ConfigParseTimezoneModeOptionValueTest : AbstractUnitFileTest() { val file = """ [Exec] Timezone=invalid - Timezone=on + Timezone=Copy Timezone=unbind - Timezone=auto + Timezone=yess """.trimIndent() // Execute SUT diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUsernsOwnershipOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUsernsOwnershipOptionValueTest.kt index b1f5c778..88fc80b1 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUsernsOwnershipOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUsernsOwnershipOptionValueTest.kt @@ -15,6 +15,8 @@ class ConfigParseUsernsOwnershipOptionValueTest : AbstractUnitFileTest() { PrivateUsersOwnership=off PrivateUsersOwnership=chown PrivateUsersOwnership=map + PrivateUsersOwnership=foreign + PrivateUsersOwnership=auto """.trimIndent() // Execute SUT @@ -43,6 +45,9 @@ class ConfigParseUsernsOwnershipOptionValueTest : AbstractUnitFileTest() { PrivateUsersOwnership=n PrivateUsersOwnership=t PrivateUsersOwnership=f + PrivateUsersOwnership=True + PrivateUsersOwnership=OFF + PrivateUsersOwnership=nO """.trimIndent() // Execute SUT @@ -61,7 +66,7 @@ class ConfigParseUsernsOwnershipOptionValueTest : AbstractUnitFileTest() { val file = """ [Files] PrivateUsersOwnership=invalid - PrivateUsersOwnership=auto + PrivateUsersOwnership=Chown PrivateUsersOwnership=chmod PrivateUsersOwnership=own """.trimIndent()