/// <summary> /// Given a group of votes (grouped by task), create and return /// a list of VoteNodes that collapse together votes that are /// sub-votes of each other. /// </summary> /// <param name="taskGroup">A set of votes with the same task value.</param> /// <returns>Returns a list of VoteNodes that collapse similar votes.</returns> public static IEnumerable<VoteNode> GetVoteNodes(IGrouping<string, KeyValuePair<string, HashSet<string>>> taskGroup) { var groupByFirstLine = taskGroup.GroupBy(v => v.Key.GetFirstLine(), Agnostic.StringComparer); List<VoteNode> nodeList = new List<VoteNode>(); VoteNode parent; foreach (var voteGroup in groupByFirstLine) { parent = null; if (voteGroup.Count() == 1) { string planname = VoteString.GetPlanName(voteGroup.Key); if (planname != null && VoteCounter.Instance.HasPlan(planname)) { var vote = voteGroup.First(); parent = new VoteNode(vote.Key, vote.Value); nodeList.Add(parent); continue; } } foreach (var vote in voteGroup) { var lines = vote.Key.GetStringLines(); if (parent == null) { var voters = lines.Count == 1 ? vote.Value : null; parent = new VoteNode(lines[0], voters); } if (lines.Count == 1) { parent.AddVoters(vote.Value); } else if (lines.Count == 2 && !string.IsNullOrEmpty(VoteString.GetVotePrefix(lines[1]))) { parent.AddChild(lines[1], vote.Value); } else { parent.AddChild(vote.Key, vote.Value); } } if (parent != null) { nodeList.Add(parent); } } return nodeList.OrderByDescending(v => v.VoterCount); }
/// <summary> /// Add the provided vote to the output in compact format. /// </summary> /// <param name="vote">The vote to add.</param> private void AddCompactVote(KeyValuePair <string, HashSet <string> > vote) { List <string> voteLines = vote.Key.GetStringLines(); if (voteLines.Count == 0) { return; } int userCount = VoteInfo.CountVote(vote); string userCountMarker = userCount.ToString(); // Single-line votes are always shown. if (voteLines.Count == 1) { sb.AppendLine(VoteString.ModifyVoteLine(voteLines.First(), marker: userCountMarker)); return; } // Two-line votes can be shown if the second line is a sub-vote. if (voteLines.Count == 2 && !string.IsNullOrEmpty(VoteString.GetVotePrefix(voteLines.Last()))) { sb.AppendLine(VoteString.ModifyVoteLine(voteLines.First(), marker: userCountMarker)); sb.AppendLine(VoteString.ModifyVoteLine(voteLines.Last(), marker: userCountMarker)); return; } // Longer votes get condensed down to a link to the original post (and named after the first voter) string firstVoter = VoteInfo.GetFirstVoter(vote.Value); string task = VoteString.GetVoteTask(vote.Key); sb.Append($"[{userCountMarker}]"); if (!string.IsNullOrEmpty(task)) { sb.Append($"[{task}]"); } string link; if (firstVoter.StartsWith(StringUtility.PlanNameMarker, StringComparison.Ordinal)) { link = VoteInfo.GetVoterUrl(firstVoter, VoteType.Plan); } else { link = VoteInfo.GetVoterUrl(firstVoter, VoteType.Vote); } sb.Append($" Plan: {firstVoter} — {link}\r\n"); }
public void GetVotePrefixTest() { string line1 = "[x] Vote for stuff"; string line2 = "-[x] Vote for stuff"; string line3 = "---[x] Vote for stuff"; string line4 = "- [x] Vote for stuff"; string line5 = "- - -[x] Vote for stuff"; Assert.AreEqual("", VoteString.GetVotePrefix(line1)); Assert.AreEqual("-", VoteString.GetVotePrefix(line2)); Assert.AreEqual("---", VoteString.GetVotePrefix(line3)); Assert.AreEqual("-", VoteString.GetVotePrefix(line4)); Assert.AreEqual("---", VoteString.GetVotePrefix(line5)); }
private bool HasChildLines(string vote) { if (string.IsNullOrEmpty(vote)) { return(false); } var voteLines = vote.GetStringLines(); if (voteLines.Count < 2) { return(false); } var topIndent = VoteString.GetVotePrefix(voteLines.First()).Length; var voteLinesPlus = voteLines.Skip(1); return(voteLinesPlus.All(a => VoteString.GetVotePrefix(a).Length > topIndent)); }
/// <summary> /// Takes the full vote string list of the vote and breaks it /// into base plans, regular vote lines, and ranked vote lines. /// Store in the local object properties. /// </summary> /// <param name="voteStrings">The list of all the lines in the vote post.</param> private void SeparateVoteStrings(List <string> voteStrings) { BasePlans = new List <IGrouping <string, string> >(); List <string> consolidatedLines = new List <string>(); // Group blocks based on parent vote lines (no prefix). // Key for each block is the parent vote line. var voteBlocks = voteStrings.GroupAdjacentToPreviousKey( (s) => string.IsNullOrEmpty(VoteString.GetVotePrefix(s)), (s) => s, (s) => s); bool addBasePlans = true; foreach (var block in voteBlocks) { if (addBasePlans) { if (block.Count() > 1) { string planName = VoteString.GetPlanName(block.Key, basePlan: true); if (planName != null && !VoteCounter.Instance.ReferenceVoters.Contains(planName, Agnostic.StringComparer)) { BasePlans.Add(block); continue; } } } addBasePlans = false; consolidatedLines.AddRange(block.ToList()); } RankLines = new List <string>(); VoteLines = new List <string>(); foreach (var line in consolidatedLines) { if (VoteString.IsRankedVote(line)) { RankLines.Add(line); } else { VoteLines.Add(line); } } // If we have ranked vote options, make sure we don't have duplicate entries, // or the same option voted on different ranks. if (RankLines.Count > 0) { var groupRankLinesMulti = RankLines.GroupBy(line => VoteString.GetVoteContent(line), Agnostic.StringComparer) .Where(group => group.Count() > 1); // If there are any, remove all but the top ranked option. foreach (var lineGroup in groupRankLinesMulti) { var topOption = lineGroup.MinObject(a => VoteString.GetVoteMarker(a)); var otherOptions = lineGroup.Where(a => a != topOption).ToList(); foreach (string otherOption in otherOptions) { RankLines.Remove(otherOption); } } } }
/// <summary> /// Partitions the child components of a vote into separate vote entries, passing /// the voters into those child entries and removing the original. /// </summary> /// <param name="vote">The vote to partition.</param> /// <param name="voteType">The type of vote.</param> /// <returns>Returns true if the process was completed, or false otherwise.</returns> public bool PartitionChildren(string vote, VoteType voteType) { if (string.IsNullOrEmpty(vote)) { return(false); } // No point in partitioning rank votes if (voteType == VoteType.Rank) { return(false); } // Make sure the provided vote exists string voteKey = GetVoteKey(vote, voteType); var votes = GetVotesCollection(voteType); if (votes.ContainsKey(voteKey) == false) { return(false); } // Construct the new votes that the vote is being partitioned into. var voteLines = vote.GetStringLines(); if (voteLines.Count < 2) { return(false); } var afterVoteLines = voteLines.Skip(1); int indentCount = afterVoteLines.Min(a => VoteString.GetVotePrefix(a).Length); var promotedVoteLines = afterVoteLines.Select(a => a.Substring(indentCount)).ToList(); var partitionedVotes = VoteConstructor.PartitionVoteStrings(promotedVoteLines, Quest, PartitionMode.ByBlock); HashSet <string> addedVotes = new HashSet <string>(); var voters = votes[voteKey]; bool votesChanged = false; foreach (var v in partitionedVotes) { var key = GetVoteKey(v, voteType); if (!votes.ContainsKey(key)) { votes[key] = new HashSet <string>(StringComparer.OrdinalIgnoreCase); votesChanged = true; } votes[key].UnionWith(voters); addedVotes.Add(key); } UndoBuffer.Push(new UndoAction(UndoActionType.PartitionChildren, voteType, GetVotersCollection(voteType), addedVotes, voteKey, voters)); Delete(vote, voteType, true); if (votesChanged) { OnPropertyChanged("Votes"); } OnPropertyChanged("VoteCounter"); return(true); }