diff --git a/common/documentation.go b/common/documentation.go index 7f5892e..0df6974 100644 --- a/common/documentation.go +++ b/common/documentation.go @@ -72,10 +72,32 @@ func (v PositiveNumberValue) CheckIsValid(value string) error { return nil } +var SimpleDuplicatesExtractor = func(value string) string { + return value +} + +var ExtractKeyDuplicatesExtractor = func(separator string) func(string) string { + return func(value string) string { + splitted := strings.Split(value, separator) + + if len(splitted) == 0 { + return "" + } + + return splitted[0] + } +} + +var DuplicatesAllowedExtractor func(string) string = nil + type ArrayValue struct { SubValue Value Separator string - AllowDuplicates bool + // If this function is nil, no duplicate check is done + // (value) => Extracted value + // This is used to extract the value from the user input, + // because you may want to preprocess the value before checking for duplicates + DuplicatesExtractor *(func(string) string) } func (v ArrayValue) GetTypeDescription() []string { @@ -89,21 +111,19 @@ func (v ArrayValue) GetTypeDescription() []string { func (v ArrayValue) CheckIsValid(value string) error { values := strings.Split(value, v.Separator) - for _, subValue := range values { - err := v.SubValue.CheckIsValid(subValue) + println(fmt.Sprintf("values: %v", values)) - if err != nil { - return err - } - } - - if !v.AllowDuplicates { - valuesOccurrences := SliceToMap(values, 0) + if v.DuplicatesExtractor != nil { + valuesOccurrences := SliceToMap( + Map(values, *v.DuplicatesExtractor), + 0, + ) // Only continue if there are actually duplicate values if len(values) != len(valuesOccurrences) { - for _, subValue := range values { - valuesOccurrences[subValue]++ + for _, duplicateRawValue := range values { + duplicateValue := (*v.DuplicatesExtractor)(duplicateRawValue) + valuesOccurrences[duplicateValue]++ } duplicateValues := FilterMapWhere(valuesOccurrences, func(_ string, value int) bool { @@ -116,6 +136,15 @@ func (v ArrayValue) CheckIsValid(value string) error { } } + + for _, subValue := range values { + err := v.SubValue.CheckIsValid(subValue) + + if err != nil { + return err + } + } + return nil } diff --git a/common/errors.go b/common/errors.go index fe4b83e..0c5bbc0 100644 --- a/common/errors.go +++ b/common/errors.go @@ -127,7 +127,7 @@ type ArrayContainsDuplicatesError struct { Duplicates []string } func (e ArrayContainsDuplicatesError) Error() string { - return fmt.Sprintf("Remove the following duplicate values: %s", strings.Join(e.Duplicates, ",")) + return fmt.Sprintf("The following values are duplicated: %s", strings.Join(e.Duplicates, ",")) } type PathDoesNotExistError struct{} diff --git a/common/extra-values.go b/common/extra-values.go index 1ad5036..f1a0bfb 100644 --- a/common/extra-values.go +++ b/common/extra-values.go @@ -72,7 +72,7 @@ func UserValue(separatorForMultiple string, enforceValues bool) Value { return enumValues } else { return ArrayValue{ - AllowDuplicates: false, + DuplicatesExtractor: &SimpleDuplicatesExtractor, SubValue: enumValues, Separator: separatorForMultiple, } diff --git a/handlers/openssh/documentation-utils.go b/handlers/openssh/documentation-utils.go new file mode 100644 index 0000000..832d8b6 --- /dev/null +++ b/handlers/openssh/documentation-utils.go @@ -0,0 +1,91 @@ +package openssh + +import ( + "config-lsp/common" + "os/exec" + "strings" +) + +var BooleanEnumValue = common.EnumValue{ + EnforceValues: true, + Values: []string{"yes", "no"}, +} + +var plusMinuxCaretPrefixes = []common.Prefix{ + { + Prefix: "+", + Meaning: "Append to the default set", + }, + { + Prefix: "-", + Meaning: "Remove from the default set", + }, + { + Prefix: "^", + Meaning: "Place at the head of the default set", + }, +} + +var ChannelTimeoutExtractor = common.ExtractKeyDuplicatesExtractor("=") + +func PrefixPlusMinusCaret(values []string) common.PrefixWithMeaningValue { + return common.PrefixWithMeaningValue{ + Prefixes: []common.Prefix{ + { + Prefix: "+", + Meaning: "Append to the default set", + }, + { + Prefix: "-", + Meaning: "Remove from the default set", + }, + { + Prefix: "^", + Meaning: "Place at the head of the default set", + }, + }, + SubValue: common.ArrayValue{ + Separator: ",", + DuplicatesExtractor: &common.SimpleDuplicatesExtractor, + SubValue: common.EnumValue{ + Values: values, + }, + }, + } +} + +var _cachedQueries map[string][]string = make(map[string][]string) + +func queryValues(query string) ([]string, error) { + cmd := exec.Command("ssh", "-Q", query) + + output, err := cmd.Output() + + if err != nil { + return []string{}, err + } + + return strings.Split(string(output), "\n"), nil +} + +func QueryOpenSSHOptions( + query string, +) ([]string, error) { + var availableQueries []string + key := query + + if _cachedQueries[key] != nil && len(_cachedQueries[key]) > 0 { + return _cachedQueries[key], nil + } else { + availableQueries, err := queryValues(query) + + if err != nil { + return []string{}, err + } + + _cachedQueries[key] = availableQueries + } + + return availableQueries, nil +} + diff --git a/handlers/openssh/documentation-values.go b/handlers/openssh/documentation-values.go deleted file mode 100644 index 1bd5d6f..0000000 --- a/handlers/openssh/documentation-values.go +++ /dev/null @@ -1,41 +0,0 @@ -package openssh - -import ( - "os/exec" - "strings" -) - -var _cachedQueries map[string][]string = make(map[string][]string) - -func queryValues(query string) ([]string, error) { - cmd := exec.Command("ssh", "-Q", query) - - output, err := cmd.Output() - - if err != nil { - return []string{}, err - } - - return strings.Split(string(output), "\n"), nil -} - -func QueryOpenSSHOptions( - query string, -) ([]string, error) { - var availableQueries []string - key := query - - if _cachedQueries[key] != nil && len(_cachedQueries[key]) > 0 { - return _cachedQueries[key], nil - } else { - availableQueries, err := queryValues(query) - - if err != nil { - return []string{}, err - } - - _cachedQueries[key] = availableQueries - } - - return availableQueries, nil -} diff --git a/handlers/openssh/documentation.go b/handlers/openssh/documentation.go index 410e69a..6807939 100644 --- a/handlers/openssh/documentation.go +++ b/handlers/openssh/documentation.go @@ -4,52 +4,6 @@ import ( "config-lsp/common" ) -var BooleanEnumValue = common.EnumValue{ - EnforceValues: true, - Values: []string{"yes", "no"}, -} - -var plusMinuxCaretPrefixes = []common.Prefix{ - { - Prefix: "+", - Meaning: "Append to the default set", - }, - { - Prefix: "-", - Meaning: "Remove from the default set", - }, - { - Prefix: "^", - Meaning: "Place at the head of the default set", - }, -} - -func PrefixPlusMinusCaret(values []string) common.PrefixWithMeaningValue { - return common.PrefixWithMeaningValue{ - Prefixes: []common.Prefix{ - { - Prefix: "+", - Meaning: "Append to the default set", - }, - { - Prefix: "-", - Meaning: "Remove from the default set", - }, - { - Prefix: "^", - Meaning: "Place at the head of the default set", - }, - }, - SubValue: common.ArrayValue{ - Separator: ",", - AllowDuplicates: false, - SubValue: common.EnumValue{ - Values: values, - }, - }, - } -} - var Options = map[string]common.Option{ "AcceptEnv": common.NewOption( `Specifies what environment variables sent by the client will be copied into the session's environ(7). See SendEnv and SetEnv in ssh_config(5) for how to configure the client. The TERM environment variable is always accepted whenever the client requests a pseudo-terminal as it is required by the protocol. Variables are specified by name, which may contain the wildcard characters ‘*’ and ‘?’. Multiple environment variables may be separated by whitespace or spread across multiple AcceptEnv directives. Be warned that some environment variables could be used to bypass restricted user environments. For this reason, care should be taken in the use of this directive. The default is not to accept any environment variables.`, @@ -73,7 +27,7 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may common.CustomValue{ FetchValue: func() common.Value { return common.ArrayValue{ - AllowDuplicates: false, + DuplicatesExtractor: &common.SimpleDuplicatesExtractor, SubValue: common.StringValue{}, Separator: " ", } @@ -113,7 +67,6 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may Values: []string{"any"}, }, common.ArrayValue{ - AllowDuplicates: true, SubValue: common.EnumValue{ EnforceValues: true, Values: []string{ @@ -157,7 +110,7 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may common.ArrayValue{ SubValue: common.StringValue{}, Separator: " ", - AllowDuplicates: false, + DuplicatesExtractor: &common.DuplicatesAllowedExtractor, }, ), "AuthorizedPrincipalsCommand": common.NewOption( @@ -200,7 +153,7 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may }, SubValue: common.ArrayValue{ Separator: ",", - AllowDuplicates: false, + DuplicatesExtractor: &common.DuplicatesAllowedExtractor, SubValue: common.StringValue{}, }, }, @@ -222,7 +175,7 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may The default is not to expire channels of any type for inactivity.`, common.ArrayValue{ Separator: " ", - AllowDuplicates: false, + DuplicatesExtractor: &ChannelTimeoutExtractor, SubValue: common.KeyValueAssignmentValue{ Separator: "=", Key: common.EnumValue{ @@ -407,7 +360,6 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may Values: []string{"none"}, }, common.ArrayValue{ - AllowDuplicates: true, Separator: " ", SubValue: common.EnumValue{ EnforceValues: true, @@ -582,7 +534,7 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may common.ArrayValue{ SubValue: common.StringValue{}, Separator: ",", - AllowDuplicates: false, + DuplicatesExtractor: &common.DuplicatesAllowedExtractor, }, }, }, @@ -631,7 +583,6 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may The verify-required option requires a FIDO key signature attest that the user was verified, e.g. via a PIN. Neither the touch-required or verify-required options have any effect for other, non-FIDO, public key types.`, common.ArrayValue{ - AllowDuplicates: true, Separator: ",", SubValue: common.EnumValue{ EnforceValues: true,