public void TokenStringBuilder_WithSingleToken(string raw, string parsed) { // arrange const string configKey = "tokenKey"; var context = new GenericTokenContext(modId => false); context.Save(new ImmutableToken(configKey, new InvariantHashSet { "value" })); // act TokenString tokenStr = new TokenString(raw, context); IContextualState diagnosticState = tokenStr.GetDiagnosticState(); // assert tokenStr.Raw.Should().Be(parsed); tokenStr.GetTokenPlaceholders(recursive: false).Should().HaveCount(1); tokenStr.GetTokenPlaceholders(recursive: false).Select(p => p.Text).Should().BeEquivalentTo("{{" + configKey + "}}"); tokenStr.HasAnyTokens.Should().BeTrue(); tokenStr.IsSingleTokenOnly.Should().BeTrue(); diagnosticState.IsReady.Should().BeTrue(); diagnosticState.UnreadyTokens.Should().BeEmpty(); diagnosticState.InvalidTokens.Should().BeEmpty(); diagnosticState.Errors.Should().BeEmpty(); }
public void TokenStringBuilder_WithTokens() { // arrange const string configKey = "configKey"; const string configValue = "A"; const string tokenKey = "season"; const string raw = " assets/{{configKey}}_{{season}}_{{ invalid }}.png "; var context = new GenericTokenContext(modId => false); context.Save(new ImmutableToken(configKey, new InvariantHashSet { configValue })); context.Save(new ImmutableToken(tokenKey, new InvariantHashSet { "A" })); // act TokenString tokenStr = new TokenString(raw, context); IContextualState diagnosticState = tokenStr.GetDiagnosticState(); // assert tokenStr.Raw.Should().Be("assets/{{configKey}}_{{season}}_{{invalid}}.png"); tokenStr.GetTokenPlaceholders(recursive: false).Should().HaveCount(3); tokenStr.GetTokenPlaceholders(recursive: false).Select(name => name.Text).Should().BeEquivalentTo(new[] { "{{configKey}}", "{{season}}", "{{invalid}}" }); tokenStr.IsReady.Should().BeFalse(); tokenStr.HasAnyTokens.Should().BeTrue(); tokenStr.IsSingleTokenOnly.Should().BeFalse(); diagnosticState.IsReady.Should().BeFalse(); diagnosticState.UnreadyTokens.Should().BeEmpty(); diagnosticState.InvalidTokens.Should().HaveCount(1).And.BeEquivalentTo("invalid"); diagnosticState.Errors.Should().BeEmpty(); }
/// <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="tokenContext">The tokens available for this content pack.</param> /// <param name="migrator">The migrator which validates and migrates content pack data.</param> /// <param name="error">An error phrase indicating why parsing failed (if applicable).</param> /// <param name="parsed">The parsed value.</param> private bool TryParseStringTokens(string rawValue, IContext tokenContext, IMigration migrator, out string error, out ITokenString parsed) { // parse parsed = new TokenString(rawValue, tokenContext); if (!migrator.TryMigrate(parsed, out error)) { return(false); } // validate unknown tokens IContextualState state = parsed.GetDiagnosticState(); if (state.InvalidTokens.Any()) { error = $"found unknown tokens ({string.Join(", ", state.InvalidTokens.OrderBy(p => p))})"; parsed = null; return(false); } // validate tokens foreach (LexTokenToken lexToken in parsed.GetTokenPlaceholders(recursive: false)) { IToken token = tokenContext.GetToken(lexToken.Name, enforceContext: false); if (token == null) { error = $"'{lexToken}' can't be used as a token because that token could not be found."; // should never happen parsed = null; return(false); } } // looks OK error = null; return(true); }
/// <summary>Get a human-readable reason that the patch isn't applied.</summary> /// <param name="patch">The patch to check.</param> private string GetReasonNotLoaded(PatchInfo patch) { if (patch.IsApplied) { return(null); } IContext tokenContext = patch.PatchContext; IContextualState state = patch.State; // state error if (state.InvalidTokens.Any()) { return($"invalid tokens: {string.Join(", ", state.InvalidTokens.OrderByIgnoreCase(p => p))}"); } if (state.UnreadyTokens.Any()) { return($"tokens not ready: {string.Join(", ", state.UnreadyTokens.OrderByIgnoreCase(p => p))}"); } if (state.Errors.Any()) { return(string.Join("; ", state.Errors)); } // conditions not matched if (!patch.MatchesContext && patch.ParsedConditions != null) { string[] failedConditions = ( from condition in patch.ParsedConditions let displayText = !condition.Is(ConditionType.HasFile) && condition.HasInput() ? $"{condition.Name}:{condition.Input.Raw}" : condition.Name orderby displayText where !condition.IsMatch(tokenContext) select $"{displayText}" ).ToArray(); if (failedConditions.Any()) { return($"conditions don't match: {string.Join(", ", failedConditions)}"); } } // fallback to unavailable tokens (should never happen due to HasMod check) if (state.UnavailableModTokens.Any()) { return($"tokens provided by an unavailable mod: {string.Join(", ", state.UnavailableModTokens.OrderByIgnoreCase(p => p))}"); } // non-matching for an unknown reason if (!patch.MatchesContext) { return("doesn't match context (unknown reason)"); } // seems fine, just not applied yet return(null); }
/// <summary>Get a human-readable reason that the patch isn't applied.</summary> /// <param name="patch">The patch to check.</param> private string GetReasonNotLoaded(PatchInfo patch) { if (patch.IsApplied) { return(null); } IContext tokenContext = patch.PatchContext; IContextualState state = patch.State; // state error if (state.InvalidTokens.Any()) { return($"invalid tokens: {string.Join(", ", state.InvalidTokens.OrderByIgnoreCase(p => p))}"); } if (state.UnavailableTokens.Any()) { return($"tokens not ready: {string.Join(", ", state.UnavailableTokens.OrderByIgnoreCase(p => p))}"); } if (state.Errors.Any()) { return(string.Join("; ", state.Errors)); } // conditions not matched if (!patch.MatchesContext && patch.ParsedConditions != null) { string[] failedConditions = ( from condition in patch.ParsedConditions let displayText = !string.IsNullOrWhiteSpace(condition.Input?.Raw) ? $"{condition.Name}:{condition.Input.Raw}" : condition.Name let isMultiValue = condition.CurrentValues.Count > 1 let displayValue = condition.Values.HasAnyTokens ? $"{condition.Values.Raw} => {string.Join(", ", condition.CurrentValues)}" : $"{string.Join(", ", condition.CurrentValues)}" orderby displayText where !condition.IsMatch(tokenContext) select $"{displayText} (should be {(isMultiValue ? "one of " : "")}{displayValue})" ).ToArray(); if (failedConditions.Any()) { return($"conditions don't match: {string.Join(", ", failedConditions)}"); } } // non-matching for an unknown reason if (!patch.MatchesContext) { return("doesn't match context (unknown reason)"); } // seems fine, just not applied yet return(null); }
/// <summary>Merge the data from another instance into this instance.</summary> /// <param name="other">The other contextual state to copy.</param> public ContextualState MergeFrom(IContextualState other) { if (other != null) { this.AddRange(this.InvalidTokens, other.InvalidTokens); this.AddRange(this.UnreadyTokens, other.UnreadyTokens); this.AddRange(this.Errors, other.Errors); } return(this); }
/// <summary>Get a human-readable reason that the patch isn't applied.</summary> /// <param name="patch">The patch to check.</param> private string GetReasonNotLoaded(PatchInfo patch) { if (patch.IsApplied) { return(null); } IContext tokenContext = patch.PatchContext; IContextualState state = patch.State; // state error if (state.InvalidTokens.Any()) { return($"invalid tokens: {string.Join(", ", state.InvalidTokens.OrderByIgnoreCase(p => p))}"); } if (state.UnavailableTokens.Any()) { return($"tokens not ready: {string.Join(", ", state.UnavailableTokens.OrderByIgnoreCase(p => p))}"); } if (state.Errors.Any()) { return(string.Join("; ", state.Errors)); } // conditions not matched if (!patch.MatchesContext && patch.ParsedConditions != null) { string[] failedConditions = ( from condition in patch.ParsedConditions let displayText = !condition.Name.Equals("HasFile", StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrWhiteSpace(condition.Input?.Raw) ? $"{condition.Name}:{condition.Input.Raw}" : condition.Name orderby displayText where !condition.IsMatch(tokenContext) select $"{displayText}" ).ToArray(); if (failedConditions.Any()) { return($"conditions don't match: {string.Join(", ", failedConditions)}"); } } // non-matching for an unknown reason if (!patch.MatchesContext) { return("doesn't match context (unknown reason)"); } // seems fine, just not applied yet return(null); }
/// <summary>Get a human-readable reason that the patch isn't applied.</summary> public virtual string?GetReasonNotLoaded() { IContextualState state = this.State; // state error if (state.InvalidTokens.Any()) { return($"invalid tokens: {string.Join(", ", state.InvalidTokens.OrderByHuman())}"); } if (state.UnreadyTokens.Any()) { return($"tokens not ready: {string.Join(", ", state.UnreadyTokens.OrderByHuman())}"); } if (state.Errors.Any()) { return(string.Join("; ", state.Errors)); } // conditions not matched if (!this.MatchesContext && this.ParsedConditions.Any()) { string[] failedConditions = ( from condition in this.ParsedConditions let displayText = !condition.Is(ConditionType.HasFile) && !string.IsNullOrWhiteSpace(condition.Input.TokenString?.Raw) ? $"{condition.Name}:{condition.Input.TokenString.Raw}" : condition.Name orderby displayText where !condition.IsMatch select $"{displayText}" ).ToArray(); if (failedConditions.Any()) { return($"conditions don't match: {string.Join(", ", failedConditions)}"); } } // fallback to unavailable tokens (should never happen due to HasMod check) if (state.UnavailableModTokens.Any()) { return($"tokens provided by an unavailable mod: {string.Join(", ", state.UnavailableModTokens.OrderByHuman())}"); } // non-matching for an unknown reason if (!this.MatchesContext) { return("doesn't match context (unknown reason)"); } // seems fine, just not applied yet return(null); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="patch">The patch to represent.</param> public PatchInfo(DisabledPatch patch) { this.ShortName = this.GetShortName(patch.ContentPack, patch.LogName); this.Type = patch.Type; this.RawTargetAsset = patch.AssetName; this.ParsedTargetAsset = null; this.ParsedConditions = null; this.ContentPack = patch.ContentPack; this.IsLoaded = false; this.MatchesContext = false; this.IsApplied = false; this.State = new ContextualState().AddErrors(patch.ReasonDisabled); }
/// <summary>Construct an instance.</summary> /// <param name="patch">The patch to represent.</param> public PatchInfo(IPatch patch) { this.ShortName = this.GetShortName(patch.ContentPack, patch.LogName); this.Type = patch.Type.ToString(); this.RawTargetAsset = patch.RawTargetAsset.Raw; this.ParsedTargetAsset = patch.RawTargetAsset; this.ParsedConditions = patch.Conditions; this.ContentPack = patch.ContentPack; this.IsLoaded = true; this.MatchesContext = patch.IsReady; this.IsApplied = patch.IsApplied; this.PatchContext = patch.GetPatchContext(); this.State = patch.GetDiagnosticState(); }
public void TokenStringBuilder_PlainString(string raw) { // act TokenString tokenStr = new TokenString(raw, new GenericTokenContext(modId => false)); IContextualState diagnosticState = tokenStr.GetDiagnosticState(); // assert tokenStr.Raw.Should().Be(raw.Trim()); tokenStr.GetTokenPlaceholders(recursive: false).Should().HaveCount(0); tokenStr.HasAnyTokens.Should().BeFalse(); diagnosticState.IsReady.Should().BeTrue(); diagnosticState.UnreadyTokens.Should().BeEmpty(); diagnosticState.InvalidTokens.Should().BeEmpty(); diagnosticState.Errors.Should().BeEmpty(); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="parsedConditions">The parsed conditions (if available).</param> /// <param name="matchesContext">Whether the patch should be applied in the current context.</param> /// <param name="state">Diagnostic info about the patch.</param> public PatchBaseInfo(Condition[] parsedConditions, bool matchesContext, IContextualState state) { this.ParsedConditions = parsedConditions; this.MatchesContext = matchesContext; this.State = state; }
/// <summary>Handle the 'patch parse' command.</summary> /// <returns>Returns whether the command was handled.</returns> private bool HandleParse(string[] args) { // get token string if (args.Length < 1 || args.Length > 2) { this.Monitor.Log("The 'patch parse' command expects one to two arguments. See 'patch help parse' for more info.", LogLevel.Error); return(true); } string raw = args[0]; string modID = args.Length >= 2 ? args[1] : null; // get context IContext context; try { context = this.GetContext(modID); } catch (KeyNotFoundException ex) { this.Monitor.Log(ex.Message, LogLevel.Error); return(true); } catch (Exception ex) { this.Monitor.Log(ex.ToString(), LogLevel.Error); return(true); } // parse value TokenString tokenStr; try { tokenStr = new TokenString(raw, context, new LogPathBuilder("console command")); } catch (LexFormatException ex) { this.Monitor.Log($"Can't parse that token value: {ex.Message}", LogLevel.Error); return(true); } catch (InvalidOperationException outerEx) when(outerEx.InnerException is LexFormatException ex) { this.Monitor.Log($"Can't parse that token value: {ex.Message}", LogLevel.Error); return(true); } catch (Exception ex) { this.Monitor.Log($"Can't parse that token value: {ex}", LogLevel.Error); return(true); } IContextualState state = tokenStr.GetDiagnosticState(); // show result StringBuilder output = new StringBuilder(); output.AppendLine(); output.AppendLine("Metadata"); output.AppendLine("----------------"); output.AppendLine($" raw value: {raw}"); output.AppendLine($" ready: {tokenStr.IsReady}"); output.AppendLine($" mutable: {tokenStr.IsMutable}"); output.AppendLine($" has tokens: {tokenStr.HasAnyTokens}"); if (tokenStr.HasAnyTokens) { output.AppendLine($" tokens used: {string.Join(", ", tokenStr.GetTokensUsed().Distinct().OrderBy(p => p, StringComparer.OrdinalIgnoreCase))}"); } output.AppendLine(); output.AppendLine("Diagnostic state"); output.AppendLine("----------------"); output.AppendLine($" valid: {state.IsValid}"); output.AppendLine($" in scope: {state.IsValid}"); output.AppendLine($" ready: {state.IsReady}"); if (state.Errors.Any()) { output.AppendLine($" errors: {string.Join(", ", state.Errors)}"); } if (state.InvalidTokens.Any()) { output.AppendLine($" invalid tokens: {string.Join(", ", state.InvalidTokens)}"); } if (state.UnreadyTokens.Any()) { output.AppendLine($" unready tokens: {string.Join(", ", state.UnreadyTokens)}"); } if (state.UnavailableModTokens.Any()) { output.AppendLine($" unavailable mod tokens: {string.Join(", ", state.UnavailableModTokens)}"); } output.AppendLine(); output.AppendLine("Result"); output.AppendLine("----------------"); output.AppendLine(!tokenStr.IsReady ? "The token string is invalid or unready." : $" The token string is valid and ready. Parsed value: \"{tokenStr}\"" ); this.Monitor.Log(output.ToString(), LogLevel.Debug); return(true); }
/********* ** Private methods *********/ /// <summary>Construct an instance.</summary> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="rawType">The raw patch type.</param> /// <param name="parsedType">The parsed patch type, if valid.</param> /// <param name="conditions">The parsed conditions (if available).</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="matchesContext">Whether the patch should be applied in the current context.</param> /// <param name="state">Diagnostic info about the patch.</param> private PatchInfo(LogPathBuilder path, string?rawType, PatchType?parsedType, Condition[] conditions, IContentPack contentPack, bool matchesContext, IContextualState state) : base(conditions, matchesContext, state) { this.Path = path; this.RawType = rawType; this.ParsedType = parsedType; this.ContentPack = contentPack; this.PathWithoutContentPackPrefix = new LogPathBuilder(path.Segments.Skip(1)); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="parsedConditions">The parsed conditions (if available).</param> /// <param name="matchesContext">Whether the patch should be applied in the current context.</param> /// <param name="state">Diagnostic info about the patch.</param> public PatchBaseInfo(Condition[]?parsedConditions, bool matchesContext, IContextualState state) { this.ParsedConditions = parsedConditions ?? Array.Empty <Condition>(); this.MatchesContext = matchesContext; this.State = state; }