Example #1
0
        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();
        }
Example #2
0
        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();
        }
Example #3
0
        /// <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);
        }
Example #4
0
        /// <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);
        }
Example #5
0
        /// <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);
        }
Example #6
0
 /// <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);
        }
Example #9
0
 /*********
 ** 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);
 }
Example #10
0
 /// <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();
 }
Example #11
0
        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();
        }
Example #12
0
 /*********
 ** 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;
 }
Example #13
0
        /// <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);
        }
Example #14
0
        /*********
        ** 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;
 }