/// <inheritdoc /> public virtual bool TryValidateValues(IInputArguments input, IInvariantSet values, IContext context, [NotNullWhen(false)] out string?error) { // 'contains' limited to true/false if (input.ReservedArgs.ContainsKey(InputArguments.ContainsKey)) { string[] invalidValues = values .Where(p => !bool.TryParse(p, out bool _)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); if (invalidValues.Any()) { error = $"invalid values ({string.Join(", ", invalidValues)}); expected 'true' or 'false' when used with 'contains'."; return(false); } error = null; return(true); } // default logic if (!this.TryValidateInput(input, out error) || !this.Values.TryValidateValues(input, values, out error)) { return(false); } error = null; return(true); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="tokenContext">The tokens available for this content pack.</param> /// <param name="forMod">The manifest for the content pack being parsed.</param> /// <param name="migrator">The migrator which validates and migrates content pack data.</param> /// <param name="installedMods">The mod IDs which are currently installed.</param> public TokenParser(IContext tokenContext, IManifest forMod, IMigration migrator, IInvariantSet installedMods) { this.Context = tokenContext; this.ForMod = forMod; this.Migrator = migrator; this.InstalledMods = installedMods; }
/// <summary>Parse a string which can contain tokens, and validate that it's valid.</summary> /// <param name="rawValue">The raw string which may contain tokens.</param> /// <param name="assumeModIds">Mod IDs to assume are installed for purposes of token validation.</param> /// <param name="path">The path to the value from the root content file.</param> /// <param name="error">An error phrase indicating why parsing failed (if applicable).</param> /// <param name="parsed">The parsed value.</param> public bool TryParseString(string?rawValue, IInvariantSet assumeModIds, LogPathBuilder path, [NotNullWhen(false)] out string?error, [NotNullWhen(true)] out IManagedTokenString?parsed) { // parse lexical bits ILexToken[] bits = this.Lexer.ParseBits(rawValue, impliedBraces: false).ToArray(); for (int i = 0; i < bits.Length; i++) { if (!this.Migrator.TryMigrate(ref bits[i], out error)) { parsed = null; return(false); } } // get token string parsed = this.CreateTokenString(bits, this.Context, path); if (!this.Migrator.TryMigrate(parsed, out error)) { return(false); } // validate tokens foreach (LexTokenToken lexToken in parsed.GetTokenPlaceholders(recursive: false)) { if (!this.TryValidateToken(lexToken, assumeModIds, out error)) { return(false); } } // looks OK error = null; return(true); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="name">The value provider name.</param> /// <param name="values">Get the current token values.</param> /// <param name="allowedValues">The allowed values (or <c>null</c> if any value is allowed).</param> /// <param name="canHaveMultipleValues">Whether the root may contain multiple values (or <c>null</c> to set it based on the given values).</param> /// <param name="isMutable">Whether to mark the value provider as mutable. The value provider will be immutable regardless, but this avoids optimizations in cases where the value provider may be replaced later.</param> public ImmutableValueProvider(string name, IInvariantSet?values, IInvariantSet?allowedValues = null, bool?canHaveMultipleValues = null, bool isMutable = false) : base(name, mayReturnMultipleValuesForRoot: false) { this.Values = values ?? InvariantSets.Empty; this.AllowedRootValues = allowedValues?.Any() == true ? allowedValues : null; this.MayReturnMultipleValuesForRoot = canHaveMultipleValues ?? (this.Values.Count > 1 || this.AllowedRootValues == null || this.AllowedRootValues.Count > 1); this.IsMutable = isMutable; }
/// <inheritdoc /> public override bool UpdateContext(IContext context) { return(this.IsChanged(this.Values, () => { return this.Values = this.MarkReady(this.IsValidInContextImpl == null || this.IsValidInContextImpl()) ? InvariantSets.From(this.FetchValues()) : InvariantSets.Empty; })); }
/// <inheritdoc /> public override bool UpdateContext(IContext context) { return(this.IsChanged(() => { bool changed = false; if (this.MarkReady(this.SaveReader.IsReady)) { // update host/local player IDs long hostId = this.SaveReader.GetPlayer(PlayerType.HostPlayer)?.UniqueMultiplayerID ?? 0; long localId = this.SaveReader.GetPlayer(PlayerType.CurrentPlayer)?.UniqueMultiplayerID ?? 0; changed |= this.HostPlayerId != hostId || this.LocalPlayerId != localId; this.HostPlayerId = hostId; this.LocalPlayerId = localId; // update values by player ID HashSet <long> removeIds = new HashSet <long>(this.Values.Keys); foreach (Farmer player in this.SaveReader.GetAllPlayers()) { // get values long id = player.UniqueMultiplayerID; IInvariantSet newValues = this.FetchValues(player); if (!this.Values.TryGetValue(id, out IInvariantSet? oldValues)) { oldValues = null; } // track changes removeIds.Remove(id); changed |= oldValues == null || this.IsChanged(oldValues, newValues); // update this.Values[id] = newValues; } // remove players if needed foreach (long id in removeIds) { this.Values.Remove(id); changed = true; } } else { this.Values.Clear(); this.HostPlayerId = 0; this.LocalPlayerId = 0; } return changed; })); }
/// <summary>Parse a JSON structure which can contain tokens, and validate that it's valid.</summary> /// <param name="rawJson">The raw JSON structure which may contain tokens.</param> /// <param name="assumeModIds">Mod IDs to assume are installed for purposes of token validation.</param> /// <param name="path">The path to the value from the root content file.</param> /// <param name="error">An error phrase indicating why parsing failed (if applicable).</param> /// <param name="parsed">The parsed value, which may be legitimately <c>null</c> even if successful.</param> public bool TryParseJson(JToken?rawJson, IInvariantSet assumeModIds, LogPathBuilder path, [NotNullWhen(false)] out string?error, out TokenizableJToken?parsed) { if (rawJson == null || rawJson.Type == JTokenType.Null) { error = null; parsed = null; return(true); } // extract mutable fields if (!this.TryInjectJsonProxyFields(rawJson, assumeModIds, path, out error, out TokenizableProxy[]? proxyFields))
/// <inheritdoc /> public override bool TryValidateValues(IInputArguments input, IInvariantSet values, IContext context, [NotNullWhen(false)] out string?error) { try { return(base.TryValidateValues(input, values, context, out error)); } catch (Exception ex) { this.Log(ex); error = null; return(true); } }
/// <inheritdoc /> public override bool UpdateContext(IContext context) { return(this.IsChanged(this.Values, () => { IEnumerable <string>?raw = this.GetValueImpl(); this.Values = raw != null ? InvariantSets.From(raw) : InvariantSets.Empty; this.MarkReady(this.Values.Any()); return this.Values; })); }
/********* ** Public methods *********/ /// <summary>Get an immutable set for the given values.</summary> /// <param name="values">The values for which to get a set.</param> public static IInvariantSet From(IEnumerable <string> values) { // shortcut predefined values switch (values) { // already an invariant set case IInvariantSet set: return(set); // use predefined set if possible case IList <string> list: switch (list.Count) { case 0: return(InvariantSets.Empty); case 1: return(InvariantSets.FromValue(list[0])); case 2 when bool.TryParse(list[0], out bool left) && bool.TryParse(list[1], out bool right): if (left != right) { return(InvariantSets.Boolean); } return(left ? InvariantSets.True : InvariantSets.False); } break; } // create custom set IInvariantSet result = values is MutableInvariantSet mutableSet ? mutableSet.GetImmutable() : new InvariantSet(values); return(result.Count == 0 ? InvariantSets.Empty : result); }
/// <inheritdoc /> public override bool HasBoundedValues(IInputArguments input, out IInvariantSet allowedValues) { allowedValues = InvariantSets.From(this.GetValues(input)); return(true); }
/// <inheritdoc /> public override bool HasBoundedValues(IInputArguments input, out IInvariantSet allowedValues) { allowedValues = this.ValidWeathers; return(true); }
/// <inheritdoc /> public override bool HasBoundedValues(IInputArguments input, out IInvariantSet allowedValues) { allowedValues = InvariantSets.From(input.PositionalArgs); return(true); }
/// <summary>Load config values from the content pack.</summary> /// <param name="contentPack">The content pack whose config file to read.</param> /// <param name="config">The config schema.</param> /// <param name="logWarning">The callback to invoke on each validation warning, passed the field name and reason respectively.</param> private void LoadConfigValues(IContentPack contentPack, InvariantDictionary <ConfigField> config, Action <string, string> logWarning) { if (!config.Any()) { return; } // read raw config InvariantDictionary <IInvariantSet> configValues = new( from entry in (contentPack.ReadJsonFile <InvariantDictionary <string> >(this.Filename) ?? new()) let key = entry.Key.Trim() let value = this.ParseCommaDelimitedField(entry.Value) select new KeyValuePair <string, IInvariantSet>(key, value) ); // remove invalid values foreach (string key in configValues.Keys.ExceptIgnoreCase(config.Keys).ToArray()) { logWarning(key, "no such field supported by this content pack."); configValues.Remove(key); } // inject default values foreach (string key in config.Keys) { ConfigField field = config[key]; if (!configValues.TryGetValue(key, out IInvariantSet? values) || (!field.AllowBlank && !values.Any())) { configValues[key] = field.DefaultValues; } } // parse each field foreach (string key in config.Keys) { ConfigField field = config[key]; // validate allow-multiple if (!field.AllowMultiple && field.Value.Count > 1) { logWarning(key, "field only allows a single value."); field.SetValue(field.DefaultValues); continue; } // validate allow-values if (field.AllowValues.Any()) { IInvariantSet invalidValues = field.Value.GetWithout(field.AllowValues); if (invalidValues.Any()) { logWarning(key, $"found invalid values ({string.Join(", ", invalidValues)}), expected: {string.Join(", ", field.AllowValues)}."); field.SetValue(field.DefaultValues); continue; } } // save values field.SetValue(configValues[key]); } }
/// <inheritdoc /> public override bool HasBoundedValues(IInputArguments input, out IInvariantSet allowedValues) { allowedValues = InvariantSets.Boolean; return(true); }
/// <summary>Override the config value.</summary> /// <param name="value">The config value to set.</param> public void SetValue(IInvariantSet value) { this.Value = value; }
/********* ** Private methods *********/ /// <summary>Parse a raw config schema for a content pack.</summary> /// <param name="rawSchema">The raw config schema.</param> /// <param name="logWarning">The callback to invoke on each validation warning, passed the field name and reason respectively.</param> /// <param name="formatVersion">The content format version.</param> private InvariantDictionary <ConfigField> LoadConfigSchema(InvariantDictionary <ConfigSchemaFieldConfig?>?rawSchema, Action <string, string> logWarning, ISemanticVersion formatVersion) { InvariantDictionary <ConfigField> schema = new(); if (rawSchema == null || !rawSchema.Any()) { return(schema); } foreach (string rawKey in rawSchema.Keys) { ConfigSchemaFieldConfig?field = rawSchema[rawKey]; if (field is null) { continue; } // validate format if (string.IsNullOrWhiteSpace(rawKey)) { logWarning(rawKey, "the config field name can't be empty."); continue; } if (rawKey.Contains(InternalConstants.PositionalInputArgSeparator) || rawKey.Contains(InternalConstants.NamedInputArgSeparator)) { logWarning(rawKey, $"the name '{rawKey}' can't have input arguments ({InternalConstants.PositionalInputArgSeparator} or {InternalConstants.NamedInputArgSeparator} character)."); continue; } // validate reserved keys if (Enum.TryParse <ConditionType>(rawKey, true, out _)) { logWarning(rawKey, $"can't use {rawKey} as a config field, because it's a reserved condition key."); continue; } // read allowed/default values IInvariantSet allowValues = this.ParseCommaDelimitedField(field.AllowValues); IInvariantSet defaultValues = this.ParseCommaDelimitedField(field.Default); // pre-1.7 behaviour if (formatVersion.IsOlderThan("1.7")) { // allowed values are required if (!allowValues.Any()) { logWarning(rawKey, $"no {nameof(ConfigSchemaFieldConfig.AllowValues)} specified (and format version is less than 1.7)."); continue; } // inject default if needed if (!defaultValues.Any() && !field.AllowBlank) { defaultValues = InvariantSets.FromValue(allowValues.First()); } } // validate allowed values if (!field.AllowBlank && !defaultValues.Any()) { logWarning(rawKey, $"if {nameof(field.AllowBlank)} is false, you must specify {nameof(field.Default)}."); continue; } if (allowValues.Any() && defaultValues.Any()) { IInvariantSet invalidValues = defaultValues.GetWithout(allowValues); if (invalidValues.Any()) { logWarning(rawKey, $"default values '{string.Join(", ", invalidValues)}' are not allowed according to {nameof(ConfigSchemaFieldConfig.AllowValues)}."); continue; } } // validate allow multiple if (!field.AllowMultiple && defaultValues.Count > 1) { logWarning(rawKey, $"can't have multiple default values because {nameof(ConfigSchemaFieldConfig.AllowMultiple)} is false."); continue; } // add to schema schema[rawKey] = new ConfigField( allowValues: allowValues, defaultValues: defaultValues, value: InvariantSets.Empty, allowBlank: field.AllowBlank, allowMultiple: field.AllowMultiple, description: field.Description, section: field.Section ); } return(schema); }