• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

KSP-CKAN / CKAN / 25022127077

27 Apr 2026 10:06PM UTC coverage: 85.902% (+0.2%) from 85.669%
25022127077

push

github

HebaruSan
Merge #4591 Layout and scaling related fixes

2005 of 2157 branches covered (92.95%)

Branch coverage included in aggregate %.

35 of 37 new or added lines in 7 files covered. (94.59%)

4 existing lines in 3 files now uncovered.

12076 of 14235 relevant lines covered (84.83%)

1.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.44
/Core/Extensions/EnumerableExtensions.cs
1
using System;
2
using System.Collections;
3
using System.Collections.Generic;
4
using System.Collections.Concurrent;
5
using System.Linq;
6
using System.Threading;
7
using System.Threading.Tasks;
8

9
namespace CKAN.Extensions
10
{
11
    public static class EnumerableExtensions
12
    {
13
        public static ConcurrentDictionary<K, V> ToConcurrentDictionary<K, V>(this IEnumerable<KeyValuePair<K, V>> pairs)
14
            where K : class
15
            => new ConcurrentDictionary<K, V>(pairs);
2✔
16

17
        public static IEnumerable<T> AsParallelIf<T>(this IEnumerable<T> source,
18
                                                     bool                parallel)
19
            => parallel ? source.AsParallel()
2✔
20
                        : source;
21

22
        // https://stackoverflow.com/a/55591477/2422988
23
        public static ParallelQuery<T> WithProgress<T>(this ParallelQuery<T> source,
24
                                                       long                  totalCount,
25
                                                       IProgress<int>?       progress)
26
        {
2✔
27
            long count       = 0;
2✔
28
            int  prevPercent = -1;
2✔
29
            return progress == null
2✔
30
                ? source
31
                : source.Select(item =>
32
                {
2✔
33
                    var percent = (int)(100 * Interlocked.Increment(ref count) / totalCount);
2✔
34
                    if (percent > prevPercent)
2✔
35
                    {
2✔
36
                        progress.Report(percent);
2✔
37
                        prevPercent = percent;
2✔
38
                    }
2✔
39
                    return item;
2✔
40
                });
2✔
41
        }
2✔
42

43
        public static IEnumerable<T> Memoize<T>(this IEnumerable<T> source)
44
            => source is Memoized<T>
2✔
45
                   // Already memoized, don't wrap another layer
46
                   ? source
47
                   : new Memoized<T>(source);
48

49
        public static void RemoveWhere<K, V>(this Dictionary<K, V> source,
50
                                             Func<KeyValuePair<K, V>, bool> predicate)
51
            where K : class
52
        {
2✔
53
            var pairs = source.ToList();
2✔
54
            foreach (var kvp in pairs)
8✔
55
            {
2✔
56
                if (predicate(kvp))
2✔
57
                {
2✔
58
                    source.Remove(kvp.Key);
2✔
59
                }
2✔
60
            }
2✔
61
        }
2✔
62

63
        public static bool IntersectsWith<T>(this IEnumerable<T> source,
64
                                             IEnumerable<T>      other)
65
            => source.Intersect(other).Any();
2✔
66

67
        /// <summary>
68
        /// Sum a sequence of TimeSpans.
69
        /// Mysteriously not defined standardly.
70
        /// </summary>
71
        /// <param name="source">Sequence of TimeSpans to sum</param>
72
        /// <returns>
73
        /// Sum of the TimeSpans
74
        /// </returns>
75
        public static TimeSpan Sum(this IEnumerable<TimeSpan> source)
76
            => source.Aggregate(TimeSpan.Zero,
2✔
77
                                (a, b) => a + b);
2✔
78

79
        /// <summary>
80
        /// Zip a sequence with a sequence generated from the first sequence using the given function
81
        /// </summary>
82
        /// <typeparam name="T">Source sequence type</typeparam>
83
        /// <typeparam name="V">Type of elements returned by func</typeparam>
84
        /// <param name="source">Source sequence</param>
85
        /// <param name="func">Function to generate values of second sequence given source</param>
86
        /// <returns>Sequence of tuples containing pairs from each sequence</returns>
87
        public static IEnumerable<(T First, V Second)> ZipBy<T, V>(this IEnumerable<T> source, Func<IEnumerable<T>, IEnumerable<V>> func)
88
            => source.ToArray() is T[] array
2✔
89
                   ? array.Zip(func(array))
90
                   : Enumerable.Empty<(T First, V Second)>();
91

92
        /// <summary>
93
        /// Insert new elements between consecutive pairs of existing elements,
94
        /// preserving the original elements in order, and using null to
95
        /// represent the elements before the beginning and after the end.
96
        /// </summary>
97
        /// <param name="source">Sequence into which to inject</param>
98
        /// <param name="inBetween">Function to generate the new elements</param>
99
        /// <returns>Sequence with new elements in it</returns>
100
        public static IEnumerable<T> Inject<T>(this IEnumerable<T> source,
101
                                               Func<T?, T?, T>     inBetween)
102
            where T : class
103
        {
2✔
104
            using (var e = source.GetEnumerator())
2✔
105
            {
2✔
106
                if (e.MoveNext())
2✔
107
                {
2✔
108
                    yield return inBetween(null, e.Current);
2✔
109
                    yield return e.Current;
2✔
110
                    var prev = e.Current;
2✔
111
                    while (e.MoveNext())
2✔
112
                    {
2✔
113
                        yield return inBetween(prev, e.Current);
2✔
114
                        yield return e.Current;
2✔
115
                        prev = e.Current;
2✔
116
                    }
2✔
117
                    yield return inBetween(prev, null);
2✔
118
                }
2✔
119
                else
120
                {
×
121
                    yield return inBetween(null, null);
×
122
                }
×
123
            }
2✔
124
        }
2✔
125

126
        /// <summary>
127
        /// Poor man's PLINQ, a trivially parallelized SelectMany that
128
        /// runs one process per item in the source sequence.
129
        /// For short sequences and long-running functions,
130
        /// when you don't feel like fighting with Partitioner.Create
131
        /// over how many items should be in each partition.
132
        /// </summary>
133
        /// <param name="source">The sequence to process</param>
134
        /// <param name="func">The function to apply to each item in the sequence</param>
135
        /// <returns>Sequence of all values from the function</returns>
136
        public static IEnumerable<V> SelectManyTasks<T, V>(this IReadOnlyCollection<T> source,
137
                                                           Func<T, IEnumerable<V>>     func)
138
        {
2✔
139
            if (source.Count <= 1)
2✔
UNCOV
140
            {
×
UNCOV
141
                return source.SelectMany(func);
×
142
            }
143
            else
144
            {
2✔
145
                var tasks = source.Select(item => Task.Run(() => func(item).ToArray()))
2✔
146
                                  // Force non-lazy creation of tasks
147
                                  .ToArray();
148
                return Utilities.WithRethrowInner(() =>
2✔
149
                {
2✔
150
                    // Without this, later tasks don't finish if an earlier one throws
151
                    Task.WaitAll(tasks);
2✔
152
                    return tasks.SelectMany(task => task.Result);
2✔
153
                });
2✔
154
            }
155
        }
2✔
156

157
        /// <summary>
158
        /// Generate a sequence from a linked list
159
        /// </summary>
160
        /// <param name="start">The first node</param>
161
        /// <param name="getNext">Function to go from one node to the next</param>
162
        /// <returns>All the nodes in the list as a sequence</returns>
163
        public static IEnumerable<T> TraverseNodes<T>(this T start, Func<T, T?> getNext)
164
            where T : class
165
        {
2✔
166
            for (T? t = start; t != null; t = Utilities.DefaultIfThrows(() => getNext(t)))
6✔
167
            {
2✔
168
                yield return t;
2✔
169
            }
2✔
170
        }
2✔
171

172
        /// <summary>
173
        /// Apply a function to a sequence and handle any exceptions that are thrown
174
        /// </summary>
175
        /// <typeparam name="TSrc">Type of source sequence</typeparam>
176
        /// <typeparam name="TDest">Type of destination sequence</typeparam>
177
        /// <param name="source">Source sequence</param>
178
        /// <param name="func">Function to apply to each item</param>
179
        /// <param name="onThrow">Function to call if there's an exception</param>
180
        /// <returns>Sequence of return values of given function</returns>
181
        public static IEnumerable<TDest?> SelectWithCatch<TSrc, TDest>(this IEnumerable<TSrc>       source,
182
                                                                       Func<TSrc, TDest>             func,
183
                                                                       Func<TSrc, Exception, TDest?> onThrow)
184
                where TDest : class
185
            => source.Select(item => Utilities.DefaultIfThrows(()  => func(item),
2✔
186
                                                               exc => onThrow(item, exc)));
2✔
187

188
        /// <summary>
189
        /// Apply a sequence-generating function to a sequence and combine the subsequences,
190
        /// skipping elements in the input sequence that throw exceptions.
191
        /// </summary>
192
        /// <param name="source">The sequence to process</param>
193
        /// <param name="func">The function that generates more subsequences</param>
194
        /// <returns>Sequence of values</returns>
195
        public static IEnumerable<V> SelectManyWithCatch<T, V>(this IEnumerable<T>     source,
196
                                                               Func<T, IEnumerable<V>> func)
197
            => source.SelectMany(elt => Utilities.DefaultIfThrows(() => func(elt))
2✔
198
                                        ?? Enumerable.Empty<V>());
199

200
        /// <summary>
201
        /// Get a hash code for a sequence with a variable number of elements
202
        /// </summary>
203
        /// <typeparam name="T">Type of the elements in the sequence</typeparam>
204
        /// <param name="source">The sequence</param>
205
        /// <returns></returns>
206
        public static int ToSequenceHashCode<T>(this IEnumerable<T> source)
207
            => source.Aggregate(new HashCode(),
2✔
208
                                (hc, item) =>
209
                                {
2✔
210
                                    hc.Add(item);
2✔
211
                                    return hc;
2✔
212
                                },
2✔
213
                                hc => hc.ToHashCode());
2✔
214

215
        /// <summary>
216
        /// Accumulate a sequence of values based on a seed value and a function,
217
        /// similar to Aggregate but including intermediate values
218
        /// </summary>
219
        /// <typeparam name="TSource"></typeparam>
220
        /// <typeparam name="TResult"></typeparam>
221
        /// <param name="source">Input sequence</param>
222
        /// <param name="seed">First intermediate value, not included in return sequence</param>
223
        /// <param name="func">Function to transform a previous result and a next input sequence element into the next result</param>
224
        /// <returns></returns>
225
        public static IEnumerable<TResult> Accumulate<TSource, TResult>(this IEnumerable<TSource>       source,
226
                                                                        TResult                         seed,
227
                                                                        Func<TResult, TSource, TResult> func)
228
        {
2✔
229
            var result = seed;
2✔
230
            foreach (var item in source)
8✔
231
            {
2✔
232
                result = func(result, item);
2✔
233
                yield return result;
2✔
234

235

236
            }
2✔
237
        }
×
238

239
        public static IEnumerable<string> ExceptContainsAny(this   IEnumerable<string> source,
240
                                                            params string[]            strings)
241
            => source.Where(elt => !strings.Any(s => elt.Contains(s)));
2✔
242

243
        public static bool IsEmptyOrAny<T>(this IReadOnlyCollection<T> source,
244
                                           Func<T, bool>               func)
245
            => source.Count == 0 || source.Any(func);
2✔
246

247
        public static Dictionary<K, T[]> ToGroupedDictionary<T, K>(this IEnumerable<T> source,
248
                                                                   Func<T, K>          keyFunc)
249
            where K : notnull
250
            => source.GroupBy(keyFunc)
2✔
251
                     .ToDictionary(grp => grp.Key,
2✔
252
                                   grp => grp.ToArray());
2✔
253

254
        public static Dictionary<K, V[]> ToGroupedDictionary<T, K, V>(this IEnumerable<T> source,
255
                                                                      Func<T, K>          keyFunc,
256
                                                                      Func<T, V>          valFunc)
257
            where K : notnull
258
            => source.GroupBy(keyFunc, valFunc)
2✔
259
                     .ToDictionary(grp => grp.Key,
2✔
260
                                   grp => grp.ToArray());
2✔
261

262
    }
263

264
    /// <summary>
265
    /// Memoized lazy evaluation in C#!
266
    /// From https://stackoverflow.com/a/12428250/2422988
267
    /// </summary>
268
    public class Memoized<T> : IEnumerable<T>
269
    {
270
        public Memoized(IEnumerable<T> source)
2✔
271
        {
2✔
272
            this.source = source;
2✔
273
        }
2✔
274

275
        IEnumerator IEnumerable.GetEnumerator()
276
            => GetEnumerator();
2✔
277

278
        public IEnumerator<T> GetEnumerator()
279
        {
2✔
280
            lock (gate)
2✔
281
            {
2✔
282
                if (isCacheComplete)
2✔
283
                {
2✔
284
                    return cache.GetEnumerator();
2✔
285
                }
286
                else
287
                {
2✔
288
                    enumerator ??= source.GetEnumerator();
2✔
289
                }
2✔
290
            }
2✔
291
            return GetMemoizingEnumerator();
2✔
292
        }
2✔
293

294
        private IEnumerator<T> GetMemoizingEnumerator()
295
        {
2✔
296
            for (int index = 0; TryGetItem(index, out T? item); ++index)
6✔
297
            {
2✔
298
                yield return item;
2✔
299
            }
2✔
300
        }
2✔
301

302
        private bool TryGetItem(int index,
303
                                out T item)
304
        {
2✔
305
            lock (gate)
2✔
306
            {
2✔
307
                if (enumerator is not null && !IsItemInCache(index))
2✔
308
                {
2✔
309
                    // The iteration may have completed while waiting for the lock
310
                    #nullable disable
311
                    if (isCacheComplete)
2✔
312
                    {
×
313
                        item = default;
×
314
                        return false;
×
315
                    }
316
                    if (!enumerator.MoveNext())
2✔
317
                    {
2✔
318
                        item = default;
2✔
319
                        isCacheComplete = true;
2✔
320
                        enumerator.Dispose();
2✔
321
                        return false;
2✔
322
                    }
323
                    #nullable enable
324
                    cache.Add(enumerator.Current);
2✔
325
                }
2✔
326
                item = cache[index];
2✔
327
                return true;
2✔
328
            }
329
        }
2✔
330

331
        private bool IsItemInCache(int index)
332
            => index < cache.Count;
2✔
333

334
        private readonly IEnumerable<T>  source;
335
        private          IEnumerator<T>? enumerator;
336
        private readonly List<T>         cache = new List<T>();
2✔
337
        private          bool            isCacheComplete;
338
        private readonly object          gate = new object();
2✔
339
    }
340
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc