예제 #1
0
        /// <summary>
        /// Gets the vote partitions of a plan.
        /// </summary>
        /// <param name="lines">The lines of a vote plan.</param>
        /// <param name="partitionMode">The partition mode being used.</param>
        /// <param name="author">The author of the post.</param>
        /// <returns>Returns the vote partitioned appropriately.</returns>
        private List <string> GetVotePartitionsFromPlan(IEnumerable <string> lines, PartitionMode partitionMode, string author)
        {
            switch (partitionMode)
            {
            case PartitionMode.None:
                // No partitioning; no special treatment
                return(PartitionByNone(lines, author));

            case PartitionMode.ByLine:
                // When partitioning by line, promote the plan first.
                // The label line can be discarded, and the others treated as less indented.
                return(PartitionByLine(PromoteLines(lines), author));

            case PartitionMode.ByLineTask:
                // When partitioning by line, promote the plan first.
                // The label line can be discarded, and the others treated as less indented.
                return(PartitionByLineTask(lines, author));

            case PartitionMode.ByBlock:
                // Normal block partitioning means we don't partition plans.
                // They will end up as a single block for the regular vote to consume.
                return(PartitionByNone(lines, author));

            case PartitionMode.ByBlockAll:
                // When partitioning by BlockAll, any plans are themselves partitioned by block (after promotion).
                // Make sure to preserve the task from the main line on the resulting blocks.
                string planTask = VoteString.GetVoteTask(lines.First());
                var    blocks   = PartitionByBlock(PromoteLines(lines), author);
                return(ApplyTaskToBlocks(blocks, planTask));

            default:
                throw new ArgumentException($"Unknown partition mode: {partitionMode}");
            }
        }
예제 #2
0
        /// <summary>
        /// Takes a list of string lines and, if the first line contains a plan
        /// name using "Base Plan", convert it to a version that only uses "Plan".
        /// </summary>
        /// <param name="lines">A list of lines defining a plan.</param>
        /// <returns>Returns the list of lines, with the assurance that
        /// any plan name starts with just "Plan".</returns>
        private static IEnumerable <string> NormalizePlanName(IEnumerable <string> lines)
        {
            string firstLine = lines.First();
            var    remainder = lines.Skip(1);

            string nameContent = VoteString.GetVoteContent(firstLine, VoteType.Plan);

            Match m = basePlanRegex.Match(nameContent);

            if (m.Success)
            {
                nameContent = $"Plan{m.Groups[1]}{m.Groups["planname"]}";

                firstLine = VoteString.ModifyVoteLine(firstLine, content: nameContent);

                List <string> results = new List <string>(lines.Count())
                {
                    firstLine
                };
                results.AddRange(remainder);

                return(results);
            }

            return(lines);
        }
예제 #3
0
        /// <summary>
        /// Add the specified task to all the provided blocks, if they don't
        /// already have a task.
        /// </summary>
        /// <param name="blocks">A list of vote blocks.</param>
        /// <param name="planTask">A task name to apply.  If no name is provided, no changes are made.</param>
        /// <returns>Returns the vote blocks with the task applied.</returns>
        private static List <string> ApplyTaskToBlocks(List <string> blocks, string planTask)
        {
            if (blocks == null)
            {
                throw new ArgumentNullException(nameof(blocks));
            }
            if (string.IsNullOrEmpty(planTask))
            {
                return(blocks);
            }

            List <string> results = new List <string>();

            foreach (var block in blocks)
            {
                if (VoteString.GetVoteTask(block).Length == 0)
                {
                    string rep = VoteString.ModifyVoteLine(block, task: planTask, byPartition: true);
                    results.Add(rep);
                }
                else
                {
                    results.Add(block);
                }
            }

            return(results);
        }
예제 #4
0
        /// <summary>
        /// Filters the plans by task.
        /// </summary>
        /// <param name="plans">The plans.</param>
        /// <param name="taskFilter">The task filter.</param>
        /// <returns>Returns the plans after filtering with the task filter.</returns>
        private static List <List <string> > FilterPlansByTask(List <List <string> > plans, IQuest quest)
        {
            if (!quest.UseCustomTaskFilters)
            {
                return(plans);
            }

            // Include lines where the task filter matches
            var filtered = plans.Where(p => quest.TaskFilter.Match(VoteString.GetVoteTask(p.First())));

            return(filtered.ToList());
        }
예제 #5
0
        /// <summary>
        /// Partition a vote by line, but carry any task on parent lines down
        /// to child lines.
        /// </summary>
        /// <param name="lines">The lines of the vote.</param>
        /// <param name="author">The author of the vote, for use in determining
        /// valid referrals.</param>
        /// <returns>Returns a list of partitioned vote lines.</returns>
        private List <string> PartitionByLineTask(IEnumerable <string> lines, string author)
        {
            List <string> partitions    = new List <string>();
            List <string> referralVotes = new List <string>();
            string        parentTask    = string.Empty;

            foreach (string line in lines)
            {
                // If someone copy/pasted a vote with a referral at the top (eg: self-named plan),
                // skip the copy/pasted section.
                if (referralVotes.Any())
                {
                    if (Agnostic.StringComparer.Equals(line, referralVotes.First()))
                    {
                        referralVotes = referralVotes.Skip(1).ToList();
                        continue;
                    }

                    referralVotes.Clear();
                }

                referralVotes = VoteCounter.GetVotesFromReference(line, author);

                if (referralVotes.Any())
                {
                    partitions.AddRange(referralVotes);

                    if (Agnostic.StringComparer.Equals(line, referralVotes.First()))
                    {
                        referralVotes = referralVotes.Skip(1).ToList();
                        continue;
                    }
                }
                else
                {
                    string taskedLine = line;

                    if (string.IsNullOrEmpty(VoteString.GetVotePrefix(line)))
                    {
                        parentTask = VoteString.GetVoteTask(line);
                    }
                    else if (string.IsNullOrEmpty(VoteString.GetVoteTask(line)))
                    {
                        taskedLine = VoteString.ModifyVoteLine(line, task: parentTask);
                    }

                    partitions.Add(taskedLine + "\r\n");
                }
            }

            return(partitions);
        }
예제 #6
0
        /// <summary>
        /// Get the name of a voter that is referenced if that is the only
        /// reference in the vote.
        /// </summary>
        /// <param name="post">The post.</param>
        /// <returns></returns>
        private string GetPureRankReference(PostComponents post)
        {
            if (post.VoteLines.Count == 1)
            {
                var refNames = VoteString.GetVoteReferenceNames(post.VoteLines.First());

                var refVoter = refNames[ReferenceType.Voter].FirstOrDefault(n => n != post.Author && VoteCounter.HasUserEnteredVoter(n, VoteType.Rank));

                return(refVoter);
            }

            return(null);
        }
예제 #7
0
        /// <summary>
        /// Store original plan name and contents in reference containers.
        /// </summary>
        /// <param name="plans">A list of valid plans.</param>
        private void StorePlanReferences(IEnumerable <List <string> > plans)
        {
            foreach (var plan in plans)
            {
                string planName  = VoteString.GetPlanName(plan.First());
                string cleanName = VoteString.RemoveBBCode(planName);
                cleanName = VoteString.DeUrlContent(cleanName);


                if (!VoteCounter.ReferencePlanNames.Contains(cleanName, Agnostic.StringComparer))
                {
                    VoteCounter.ReferencePlanNames.Add(cleanName);
                    VoteCounter.ReferencePlans[cleanName] = plan;
                }
            }
        }
예제 #8
0
        /// <summary>
        /// If all sub-lines of a provided group of lines are indented (have a prefix),
        /// then 'promote' them up a tier (remove one level of the prefix) while discarding
        /// the initial line.
        /// </summary>
        /// <param name="lines">A list of strings to examine/promote.</param>
        /// <returns>Returns the strings without the initial line, and with the
        /// remaining lines reduced by one indent level.</returns>
        private static IEnumerable <string> PromoteLines(IEnumerable <string> lines)
        {
            if (lines == null)
            {
                throw new ArgumentNullException(nameof(lines));
            }

            var remainder = lines.Skip(1);

            if (remainder.All(l => VoteString.GetVotePrefix(l).Length > 0))
            {
                return(remainder.Select(l => l.Substring(1).Trim()));
            }

            return(remainder);
        }
예제 #9
0
        /// <summary>
        /// Gets a list of all full-vote plans (of which there will only be one, if found).
        /// </summary>
        /// <param name="post">The post to extract plans from.</param>
        /// <returns>Returns a list of plans (which are lists of vote lines).</returns>
        private List <List <string> > GetAllFullPostPlans(PostComponents post)
        {
            List <List <string> > results = new List <List <string> >();

            if (post.VoteLines.Any())
            {
                // Group blocks based on parent vote lines (no prefix).
                // Key for each block is the parent vote line.
                var voteBlocks = post.VoteLines.GroupAdjacentToPreviousKey(
                    (s) => string.IsNullOrEmpty(VoteString.GetVotePrefix(s)),
                    (s) => s,
                    (s) => s);

                // If the vote has any plans with content in them, we can't make this a full-post plan.
                if (!voteBlocks.Any(b => b.Count() > 1 && VoteString.GetPlanName(b.Key) != null))
                {
                    // The post must have more than one line to count for a plan label.
                    if (post.VoteLines.Count > 1)
                    {
                        var firstLine = post.VoteLines.First();

                        string planname = VoteString.GetPlanName(firstLine);

                        if (planname != null)
                        {
                            // If it's named after a user, it must be the post author.  Otherwise, anything is fine.
                            if (VoteCounter.ReferenceVoters.Contains(planname, Agnostic.StringComparer))
                            {
                                if (Agnostic.StringComparer.Equals(planname, post.Author))
                                {
                                    results.Add(post.VoteLines);
                                }
                            }
                            else
                            {
                                results.Add(post.VoteLines);
                            }
                        }
                    }
                }
            }

            return(results);
        }
예제 #10
0
        /// <summary>
        /// Gets a list of all plans within the post that have defined content (child lines).
        /// </summary>
        /// <param name="post">The post to extract plans from.</param>
        /// <returns>Returns a list of plans (which are lists of vote lines).</returns>
        private List <List <string> > GetAllPlansWithContent(PostComponents post)
        {
            List <List <string> > results = new List <List <string> >();

            results.AddRange(post.BasePlans.Select(a => a.ToList()));

            if (post.VoteLines.Any())
            {
                // Group blocks based on parent vote lines (no prefix).
                // Key for each block is the parent vote line.
                var voteBlocks = post.VoteLines.GroupAdjacentToPreviousKey(
                    (s) => string.IsNullOrEmpty(VoteString.GetVotePrefix(s)),
                    (s) => s,
                    (s) => s);

                foreach (var block in voteBlocks)
                {
                    if (block.Count() > 1)
                    {
                        string planname = VoteString.GetPlanName(block.Key);

                        if (planname != null)
                        {
                            // Add a named vote that is named after a user only if it matches the post author's name.
                            if (VoteCounter.ReferenceVoters.Contains(planname, Agnostic.StringComparer))
                            {
                                if (Agnostic.StringComparer.Equals(planname, post.Author))
                                {
                                    results.Add(block.ToList());
                                }
                            }
                            else
                            {
                                // If it's not named after a user, add it normally.
                                results.Add(block.ToList());
                            }
                        }
                    }
                }
            }

            return(results);
        }
예제 #11
0
        /// <summary>
        /// Put any plans found in the grouped vote lines into the standard tracking sets,
        /// after handling any partitioning needed.
        /// </summary>
        /// <param name="plans">List of plans to be processed.</param>
        /// <param name="post">Post the plans were pulled from.</param>
        /// <param name="partitionMode">Partition mode being used.</param>
        private void ProcessPlans(IEnumerable <List <string> > plans, PostComponents post, PartitionMode partitionMode)
        {
            foreach (var plan in plans)
            {
                string planName  = VoteString.GetMarkedPlanName(plan.First());
                string cleanName = VoteString.RemoveBBCode(planName);
                cleanName = VoteString.DeUrlContent(cleanName);

                if (!VoteCounter.HasPlan(cleanName))
                {
                    var nPlan = NormalizePlanName(plan);

                    // Get the list of all vote partitions, built according to current preferences.
                    // One of: By line, By block, or By post (ie: entire vote)
                    var votePartitions = GetVotePartitions(nPlan, partitionMode, VoteType.Plan, post.Author);

                    VoteCounter.AddVotes(votePartitions, cleanName, post.ID, VoteType.Plan);
                }
            }
        }
예제 #12
0
        /// <summary>
        /// Gets a list of all full-vote plans (of which there will only be one, if found).
        /// </summary>
        /// <param name="post">The post to extract plans from.</param>
        /// <returns>Returns a list of plans (which are lists of vote lines).</returns>
        private List <List <string> > GetAllOneLinePlans(PostComponents post)
        {
            List <List <string> > results = new List <List <string> >();

            if (post.VoteLines.Any())
            {
                // Group blocks based on parent vote lines (no prefix).
                // Key for each block is the parent vote line.
                var voteBlocks = post.VoteLines.GroupAdjacentToPreviousKey(
                    (s) => string.IsNullOrEmpty(VoteString.GetVotePrefix(s)),
                    (s) => s,
                    (s) => s);

                foreach (var block in voteBlocks)
                {
                    if (block.Count() == 1)
                    {
                        string planname = VoteString.GetPlanName(block.Key);

                        if (planname != null)
                        {
                            if (VoteCounter.ReferenceVoters.Contains(planname, Agnostic.StringComparer))
                            {
                                if (Agnostic.StringComparer.Equals(planname, post.Author))
                                {
                                    results.Add(block.ToList());
                                }
                            }
                            else
                            {
                                results.Add(block.ToList());
                            }
                        }
                    }
                }
            }

            return(results);
        }
예제 #13
0
        /// <summary>
        /// Filters the votes by task.
        /// </summary>
        /// <param name="lines">The lines.</param>
        /// <param name="taskFilter">The task filter.</param>
        /// <returns>Returns the votes after filtering with the task filter.</returns>
        private static List <string> FilterVotesByTask(List <string> lines, IQuest quest)
        {
            if (!quest.UseCustomTaskFilters)
            {
                return(lines);
            }

            List <string> results = new List <string>();

            foreach (var line in lines)
            {
                string firstLine = line.GetFirstLine();
                string task      = VoteString.GetVoteTask(firstLine);
                bool   check     = quest.TaskFilter.Match(task);
                if (check)
                {
                    results.Add(line);
                }
            }

            return(results);
        }
예제 #14
0
        public static VoteLine?ParseLine(ReadOnlySpan <char> line)
        {
            if (line.Length == 0)
            {
                return(null);
            }

            StringBuilder prefixSB    = new StringBuilder();
            StringBuilder markerSB    = new StringBuilder();
            StringBuilder taskSB      = new StringBuilder();
            StringBuilder contentSB   = new StringBuilder();
            StringBuilder tempContent = new StringBuilder();

            MarkerType markerType  = MarkerType.None;
            int        markerValue = 0;

            Stack <TokenState> state        = new Stack <TokenState>();
            TokenState         currentState = TokenState.None;

            for (int c = 0; c < line.Length; c++)
            {
                char ch = line[c];

                // Skip newlines entirely, if they somehow get into the line we're parsing.
                if (newlineChars.Contains(ch))
                {
                    continue;
                }

                switch (currentState)
                {
                case TokenState.None:
                    if (ch == whitespace)
                    {
                        continue;
                    }
                    else if (prefixChars.Contains(ch))
                    {
                        prefixSB.Append(ch);
                        currentState = TokenState.Prefix;
                    }
                    else if (ch == openBracket)
                    {
                        currentState = TokenState.Marker;
                    }
                    else if (ch == xBox || ch == checkBox)
                    {
                        // Shortcut for a complete marker
                        markerSB.Append(ch);
                        (markerType, markerValue) = GetMarkerType(markerSB.ToString());
                        currentState = TokenState.PostMarker;
                    }
                    else if (ch == openBBCode)
                    {
                        state.Push(currentState);
                        currentState = TokenState.BBCode;
                    }
                    else
                    {
                        goto doneExamining;
                    }
                    break;

                case TokenState.Prefix:
                    if (ch == whitespace)
                    {
                        continue;
                    }
                    else if (prefixChars.Contains(ch))
                    {
                        prefixSB.Append(ch);
                    }
                    else if (ch == openBracket)
                    {
                        currentState = TokenState.Marker;
                    }
                    else if (ch == xBox || ch == checkBox)
                    {
                        // Shortcut for a complete marker
                        markerSB.Append(ch);
                        (markerType, markerValue) = GetMarkerType(markerSB.ToString());
                        currentState = TokenState.PostMarker;
                    }
                    else if (ch == openBBCode)
                    {
                        state.Push(currentState);
                        currentState = TokenState.BBCode;
                    }
                    else
                    {
                        goto doneExamining;
                    }
                    break;

                case TokenState.Marker:
                    if (ch == whitespace)
                    {
                        continue;
                    }
                    else if (markerChars.Contains(ch))
                    {
                        markerSB.Append(ch);
                    }
                    else if (ch == closeBracket)
                    {
                        (markerType, markerValue) = GetMarkerType(markerSB.ToString());
                        if (markerType != MarkerType.None)
                        {
                            currentState = TokenState.PostMarker;
                        }
                        else
                        {
                            goto doneExamining;
                        }
                    }
                    else if (ch == openBBCode)
                    {
                        state.Push(currentState);
                        currentState = TokenState.BBCode;
                    }
                    else
                    {
                        goto doneExamining;
                    }
                    break;

                case TokenState.PostMarker:
                    if (ch == whitespace)
                    {
                        if (tempContent.Length > 0)
                        {
                            tempContent.Append(ch);
                        }

                        continue;
                    }
                    else if (ch == openBracket && taskSB.Length == 0)
                    {
                        state.Push(currentState);
                        currentState = TokenState.Task;
                    }
                    else if (ch == openBBCode && taskSB.Length == 0)
                    {
                        state.Push(currentState);
                        currentState = TokenState.BBCode;
                        tempContent.Append(ch);
                    }
                    else if (ch == openStrike)
                    {
                        tempContent.Append("『s』");
                        state.Push(currentState);
                        currentState = TokenState.Strike;
                    }
                    else
                    {
                        contentSB.Append(tempContent);
                        tempContent.Clear();
                        contentSB.Append(ch);
                        currentState = TokenState.Content;
                    }
                    break;

                case TokenState.Task:
                    tempContent.Clear();
                    if (ch == closeBracket)
                    {
                        currentState = state.Pop();
                    }
                    else if (ch == openBBCode)
                    {
                        state.Push(currentState);
                        currentState = TokenState.BBCode;
                    }
                    else if (ch == openStrike)
                    {
                        state.Push(currentState);
                        currentState = TokenState.Strike;
                    }
                    else
                    {
                        taskSB.Append(ch);
                    }
                    break;

                case TokenState.Content:
                    if (tempContent.Length > 0)
                    {
                        contentSB.Append(tempContent);
                        tempContent.Clear();
                    }

                    if (ch == openStrike)
                    {
                        tempContent.Append("『s』");
                        state.Push(currentState);
                        currentState = TokenState.Strike;
                    }
                    else if (apostraphes.Contains(ch))
                    {
                        contentSB.Append('\'');
                    }
                    else if (quotations.Contains(ch))
                    {
                        contentSB.Append('"');
                    }
                    else
                    {
                        contentSB.Append(ch);
                    }
                    break;

                case TokenState.BBCode:
                    if (state.Peek() == TokenState.PostMarker)
                    {
                        tempContent.Append(ch);
                    }
                    if (ch == closeBBCode)
                    {
                        currentState = state.Pop();
                    }
                    break;

                case TokenState.Strike:
                    // Strike-through text is only preserved in the content area
                    if (ch == closeStrike)
                    {
                        tempContent.Append("『/s』");
                        currentState = state.Pop();
                    }
                    else if (ch == strikeNewline)
                    {
                        // If we hit embedded newlines, bail out entirely.
                        // Take whatever's been done up to that point.
                        tempContent.Clear();
                        currentState = state.Pop();
                        goto doneExamining;
                    }
                    else
                    {
                        tempContent.Append(ch);
                    }
                    break;

                default:
                    throw new InvalidOperationException($"Unknown token state value: {currentState}.");
                }
            }

doneExamining:

            if (currentState == TokenState.Content)
            {
                string content = VoteString.NormalizeContentBBCode(contentSB.ToString());
                return(new VoteLine(prefixSB.ToString(), markerSB.ToString(), taskSB.ToString(), content, markerType, markerValue));
            }

            return(null);
        }
예제 #15
0
        /// <summary>
        /// Get the lines of the vote that we will be processing out of the post.
        /// Only take the .VoteLines, and condense any instances of known plans
        /// to just a reference to the plan name.
        /// </summary>
        /// <param name="post">The post we're getting the vote from.</param>
        /// <returns>Returns the vote with plans compressed.</returns>
        public List <string> GetWorkingVote(PostComponents post)
        {
            List <string> vote = new List <string>();

            if (post == null || !post.IsVote)
            {
                return(vote);
            }

            // First determine if any base plans are copies of an original definition, or being defined in this post.
            // If they're just copies, then embed them in the working vote.

            if (post.BasePlans.Any())
            {
                var    voters    = VoteCounter.GetVotersCollection(VoteType.Plan);
                bool   checkPlan = true;
                string planName;

                foreach (var bPlan in post.BasePlans)
                {
                    planName = VoteString.GetMarkedPlanName(bPlan.Key);
                    if (planName == null)
                    {
                        continue;
                    }

                    // As long as we keep finding base plans that are defined in this post, keep skipping.
                    if (checkPlan)
                    {
                        if (VoteCounter.HasPlan(planName) && voters[planName] == post.ID)
                        {
                            continue;
                        }
                    }

                    checkPlan = false;

                    // If we reach here, any further plans are copy/pastes of defined plans, and should
                    // have the key added to the working vote.
                    vote.Add(bPlan.Key);
                }
            }

            // Then make sure there are actual vote lines to process.
            if (!post.VoteLines.Any())
            {
                return(vote);
            }

            // Then check if the *entire post* should be treated as a complete plan.
            string postPlanName = VoteString.GetPlanName(post.VoteLines.First());

            if (postPlanName != null && VoteCounter.ReferencePlans.ContainsKey(postPlanName) &&
                VoteCounter.ReferencePlans[postPlanName].Skip(1).SequenceEqual(post.VoteLines.Skip(1), Agnostic.StringComparer))
            {
                // Replace known plans with just the plan key.  They'll be expanded later.
                vote.Add(post.VoteLines.First());
            }
            else
            {
                // If the entire post isn't an auto-plan, break it down into blocks.

                // Break the remainder of the vote into blocks so that we can compare vs auto-plans.
                // Group blocks based on parent vote lines (no prefix).
                // Key for each block is the parent vote line.
                var voteBlocks = post.VoteLines.GroupAdjacentToPreviousKey(
                    (s) => string.IsNullOrEmpty(VoteString.GetVotePrefix(s)),
                    (s) => s,
                    (s) => s);


                foreach (var block in voteBlocks)
                {
                    // Multi-line blocks might be a plan.  Check.
                    if (block.Count() > 1)
                    {
                        // See if the block key marks a known plan.
                        string planName = VoteString.GetPlanName(block.Key);

                        if (planName != null && VoteCounter.ReferencePlans.ContainsKey(planName) &&
                            VoteCounter.ReferencePlans[planName].Skip(1).SequenceEqual(block.Skip(1), Agnostic.StringComparer))
                        {
                            // Replace known plans with just the plan key.  They'll be expanded later.
                            vote.Add(block.Key);
                        }
                        else
                        {
                            // If it's not a known plan, pass everything through.
                            vote.AddRange(block);
                        }
                    }
                    else
                    {
                        // Single lines can be added normally
                        vote.AddRange(block);
                        //vote.Add(block.Key);
                    }
                }
            }

            return(vote);
        }
예제 #16
0
        /// <summary>
        /// Partition the provided vote into individual partitions, by block.
        /// Referral votes are added as their own partitioned form.
        /// </summary>
        /// <param name="lines">The lines of a vote.</param>
        /// <param name="author">The author of the post.</param>
        /// <returns>Returns a the vote partitioned by block.</returns>
        private List <string> PartitionByBlock(IEnumerable <string> lines, string author)
        {
            List <string> partitions    = new List <string>();
            List <string> referralVotes = new List <string>();
            StringBuilder sb            = new StringBuilder();

            foreach (string line in lines)
            {
                // If someone copy/pasted a vote with a referral at the top (eg: self-named plan),
                // skip the copy/pasted section.
                if (referralVotes.Any())
                {
                    if (Agnostic.StringComparer.Equals(line, referralVotes.First()))
                    {
                        referralVotes = referralVotes.Skip(1).ToList();
                        continue;
                    }

                    referralVotes.Clear();
                }

                referralVotes = VoteCounter.GetVotesFromReference(line, author);

                if (referralVotes.Any())
                {
                    if (sb.Length > 0)
                    {
                        partitions.Add(sb.ToString());
                        sb.Clear();
                    }

                    partitions.AddRange(referralVotes);

                    referralVotes = referralVotes.SelectMany(a => a.GetStringLines()).ToList();
                    if (Agnostic.StringComparer.Equals(line, referralVotes.First()))
                    {
                        referralVotes = referralVotes.Skip(1).ToList();
                        continue;
                    }
                }
                else
                {
                    string prefix = VoteString.GetVotePrefix(line);

                    // If we encountered a new top-level vote line, store any existing stringbuilder contents.
                    if (string.IsNullOrEmpty(prefix) && sb.Length > 0)
                    {
                        partitions.Add(sb.ToString());
                        sb.Clear();
                    }

                    sb.AppendLine(line);
                }
            }

            if (sb.Length > 0)
            {
                partitions.Add(sb.ToString());
            }

            return(partitions);
        }
예제 #17
0
        /// <summary>
        /// Determine if there are any references to future (unprocessed) user votes
        /// within the current vote.
        /// </summary>
        /// <param name="post">Post containing the current vote.</param>
        /// <returns>Returns true if a future reference is found. Otherwise false.</returns>
        private bool HasFutureReference(PostComponents post, IQuest quest)
        {
            // If we decide it has to be forced, ignore all checks in here.
            if (post.ForceProcess)
            {
                return(false);
            }

            // If proxy votes are disabled, we don't need to look for proxy names, so there can't be future references.
            // Likewise, if we're forcing all proxy votes to be pinned, there can't be any future references.
            if (quest.DisableProxyVotes || quest.ForcePinnedProxyVotes)
            {
                return(false);
            }

            foreach (var line in post.WorkingVote)
            {
                // Get the possible proxy references this line contains
                var refNames = VoteString.GetVoteReferenceNames(line);

                // Pinned references (^ or pin keywords) are explicitly not future references
                if (refNames[ReferenceType.Label].Any(a => a == "^" || a == "pin"))
                {
                    continue;
                }

                // Any references to plans automatically work, as they are defined in a preprocess phase.
                if (refNames[ReferenceType.Plan].Any(VoteCounter.HasPlan))
                {
                    continue;
                }

                string refVoter = refNames[ReferenceType.Voter].FirstOrDefault(n => VoteCounter.ReferenceVoters.Contains(n, Agnostic.StringComparer))
                                  ?.AgnosticMatch(VoteCounter.ReferenceVoters);

                if (refVoter != null && refVoter != post.Author)
                {
                    var refVoterPosts = VoteCounter.PostsList.Where(p => p.Author == refVoter).ToList();

                    // If ref voter has no posts (how did we get here?), it can't be a future reference.
                    if (!refVoterPosts.Any())
                    {
                        continue;
                    }

                    // If the referenced voter never made a real vote (eg: only made base plans or rank votes),
                    // then this can't be treated as a future reference.
                    var refWorkingVotes = refVoterPosts.Where(p => p.WorkingVote.Count > 0);

                    if (!refWorkingVotes.Any())
                    {
                        continue;
                    }

                    // If there's no 'plan' label, then we need to verify that the last vote that the
                    // ref voter made (has a working vote) was processed.
                    // If it's been processed, then we're OK to let this vote through.
                    if (refWorkingVotes.Last().Processed)
                    {
                        continue;
                    }

                    // If none of the conditions above are met, then consider this a future reference.
                    return(true);
                }
            }

            // No future references were found.
            return(false);
        }