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

HicServices / RDMP / 6245535001

20 Sep 2023 07:44AM UTC coverage: 57.013%. First build
6245535001

push

github

web-flow
8.1.0 Release (#1628)

* Bump Newtonsoft.Json from 13.0.1 to 13.0.2

Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 13.0.1 to 13.0.2.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/13.0.1...13.0.2)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump NLog from 5.0.5 to 5.1.0

Bumps [NLog](https://github.com/NLog/NLog) from 5.0.5 to 5.1.0.
- [Release notes](https://github.com/NLog/NLog/releases)
- [Changelog](https://github.com/NLog/NLog/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/NLog/NLog/compare/v5.0.5...v5.1.0)

---
updated-dependencies:
- dependency-name: NLog
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump NLog from 5.0.5 to 5.1.0

* Fix -r flag - should have been --results-directory all along

* Bump Newtonsoft.Json from 13.0.1 to 13.0.2

* Bump YamlDotNet from 12.0.2 to 12.1.0

Bumps [YamlDotNet](https://github.com/aaubry/YamlDotNet) from 12.0.2 to 12.1.0.
- [Release notes](https://github.com/aaubry/YamlDotNet/releases)
- [Commits](https://github.com/aaubry/YamlDotNet/compare/v12.0.2...v12.1.0)

---
updated-dependencies:
- dependency-name: YamlDotNet
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump Moq from 4.18.2 to 4.18.3

Bumps [Moq](https://github.com/moq/moq4) from 4.18.2 to 4.18.3.
- [Release notes](https://github.com/moq/moq4/releases)
- [Changelog](https://github.com/moq/moq4/blob/main/CHANGELOG.md)
- [Commits](https://github.com/moq/moq4/compare/v4.18.2...v4.18.3)

---
updated-dependencies:
- dependency-name: Moq
... (continued)

10732 of 20257 branches covered (0.0%)

Branch coverage included in aggregate %.

48141 of 48141 new or added lines in 1086 files covered. (100.0%)

30685 of 52388 relevant lines covered (58.57%)

7387.88 hits per line

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

86.61
/Rdmp.Core/ReusableLibraryCode/Comments/CommentStore.cs
1
// Copyright (c) The University of Dundee 2018-2019
2
// This file is part of the Research Data Management Platform (RDMP).
3
// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4
// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5
// You should have received a copy of the GNU General Public License along with RDMP. If not, see <https://www.gnu.org/licenses/>.
6

7
using System;
8
using System.Collections;
9
using System.Collections.Generic;
10
using System.Globalization;
11
using System.IO;
12
using System.Linq;
13
using System.Text;
14
using System.Text.RegularExpressions;
15
using System.Xml;
16
using LibArchive.Net;
17

18
namespace Rdmp.Core.ReusableLibraryCode.Comments;
19

20
/// <summary>
21
/// Records documentation for classes and keywords (e.g. foreign key names).
22
/// </summary>
23
public class CommentStore : IEnumerable<KeyValuePair<string, string>>
24
{
25
    private readonly Dictionary<string, string> _dictionary = new(StringComparer.CurrentCultureIgnoreCase);
896✔
26

27
    private string[] _ignoreHelpFor =
896✔
28
    {
896✔
29
        "CsvHelper.xml",
896✔
30
        "Google.Protobuf.xml",
896✔
31
        "MySql.Data.xml",
896✔
32
        "Newtonsoft.Json.xml",
896✔
33
        "NLog.xml",
896✔
34
        "NuDoq.xml",
896✔
35
        "ObjectListView.xml",
896✔
36
        "QuickGraph.xml",
896✔
37
        "Renci.SshNet.xml",
896✔
38
        "ScintillaNET.xml",
896✔
39
        "nunit.framework.xml"
896✔
40
    };
896✔
41

42
    public virtual void ReadComments(params string[] locations)
43
    {
44
        foreach (var location in locations)
56✔
45
        {
46
            if (location is null)
14✔
47
                continue;
48
            if (Directory.Exists(location))
14!
49
                foreach (var xml in Directory.EnumerateFiles(location, "*.xml", SearchOption.AllDirectories))
180✔
50
                    using (var content = File.OpenRead(xml))
76✔
51
                    {
52
                        ReadComments(content);
76✔
53
                    }
76✔
54
            else if (File.Exists(location))
×
55
                using (var zip = new LibArchiveReader(location))
×
56
                {
57
                    foreach (var xml in zip.Entries())
×
58
                        if (xml.Name.EndsWith(".xml", true, CultureInfo.InvariantCulture))
×
59
                            using (var content = xml.Stream)
×
60
                            {
61
                                ReadComments(content);
×
62
                            }
×
63
                }
64
        }
65
    }
14✔
66

67
    private void ReadComments(Stream filename)
68
    {
69
        var doc = new XmlDocument();
76✔
70
        doc.Load(filename);
76✔
71
        doc.IterateThroughAllNodes(AddXmlDoc);
76✔
72
    }
76✔
73

74
    /// <summary>
75
    /// Adds the given member xml doc to the <see cref="CommentStore"/>
76
    /// </summary>
77
    /// <param name="obj"></param>
78
    public void AddXmlDoc(XmlNode obj)
79
    {
80
        if (obj == null)
378,716✔
81
            return;
2✔
82

83
        if (obj.Name != "member" || obj.Attributes == null) return;
678,932✔
84
        var memberName = obj.Attributes["name"]?.Value;
78,496!
85
        var summary = GetSummaryAsText(obj["summary"]);
78,496✔
86

87
        if (memberName == null || string.IsNullOrWhiteSpace(summary))
78,496✔
88
            return;
16,532✔
89

90
        //it's a Property get Type.Property (not fully specified)
91
        if (memberName.StartsWith("P:") || memberName.StartsWith("T:"))
61,964✔
92
            Add(GetLastTokens(memberName), summary.Trim());
33,460✔
93
    }
61,964✔
94

95
    private static string GetSummaryAsText(XmlElement summaryTag)
96
    {
97
        if (summaryTag == null)
78,496✔
98
            return null;
16,344✔
99

100
        var sb = new StringBuilder();
62,152✔
101

102
        summaryTag.IterateThroughAllNodes(
62,152✔
103
            n =>
62,152✔
104
            {
62,152✔
105
                switch (n.Name)
155,724✔
106
                {
62,152✔
107
                    case "see" when n.Attributes != null:
35,240✔
108
                        sb.Append($"{GetLastTokens(n.Attributes["cref"]?.Value)} "); // a <see cref="omg"> tag
35,240!
109
                        break;
35,240✔
110
                    case "para":
62,152✔
111
                        TrimEndSpace(sb)
7,404✔
112
                            .Append(Environment.NewLine +
7,404✔
113
                                    Environment.NewLine); //open para tag (next tag is probably #text)
7,404✔
114
                        break;
7,404✔
115
                    default:
62,152✔
116
                        {
62,152✔
117
                            if (n.Value != null) //e.g. #text
113,080✔
118
                                sb.Append($"{TrimSummary(n.Value)} ");
104,722✔
119
                            break;
62,152✔
120
                        }
62,152✔
121
                }
62,152✔
122
            });
175,232✔
123

124
        return sb.ToString();
62,152✔
125
    }
126

127
    private static string TrimSummary(string value) => value == null ? null : Regex.Replace(value, @"\s+", " ").Trim();
104,722!
128

129
    /// <summary>
130
    /// Returns the last x parts from a string like M:Abc.Def.Geh.AAA(fff,mm).  In this case it would return AAA for 1, Geh.AAA for 2 etc.
131
    /// </summary>
132
    /// <param name="memberName"></param>
133
    /// <param name="partsToGet"></param>
134
    /// <returns></returns>
135
    private static string GetLastTokens(string memberName, int partsToGet)
136
    {
137
        //throw away any preceding "T:", "M:" etc
138
        memberName = memberName[(memberName.IndexOf(':') + 1)..];
67,438✔
139

140
        var idxBracket = memberName.LastIndexOf('(');
67,438✔
141
        if (idxBracket != -1)
67,438✔
142
            memberName = memberName[..idxBracket];
1,266✔
143

144
        var matches = memberName.Split('.');
67,438✔
145

146
        return matches.Length < partsToGet
67,438!
147
            ? memberName
67,438✔
148
            : string.Join(".", matches.Reverse().Take(partsToGet).Reverse());
67,438✔
149
    }
150

151
    private static string GetLastTokens(string memberName)
152
    {
153
        if (memberName.StartsWith("P:"))
68,700✔
154
            return GetLastTokens(memberName, 2);
24,958✔
155

156
        if (memberName.StartsWith("T:"))
43,742✔
157
            return GetLastTokens(memberName, 1);
40,288✔
158

159
        return memberName.StartsWith("M:") ? GetLastTokens(memberName, 2) : memberName;
3,454✔
160
    }
161

162
    public static StringBuilder TrimEndSpace(StringBuilder sb)
163
    {
164
        if (sb == null || sb.Length == 0) return sb;
7,720✔
165

166
        var i = sb.Length - 1;
7,088✔
167
        for (; i >= 0; i--)
21,264✔
168
            if (sb[i] != ' ')
14,176✔
169
                break;
170

171
        if (i < sb.Length - 1)
7,088✔
172
            sb.Length = i + 1;
7,088✔
173

174
        return sb;
7,088✔
175
    }
176

177
    public void Add(string name, string summary)
178
    {
179
        //these are not helpful!
180
        if (name == "C" || name == "R")
33,652!
181
            return;
×
182

183
        if (_dictionary.ContainsKey(name))
33,652✔
184
            return;
740✔
185

186
        _dictionary.Add(name, summary);
32,912✔
187
    }
32,912✔
188

189
    public bool ContainsKey(string keyword) => _dictionary.ContainsKey(keyword);
424✔
190

191
    /// <summary>
192
    /// Returns documentation for the keyword or null if no documentation exists
193
    /// </summary>
194
    /// <param name="index"></param>
195
    /// <returns></returns>
196
    public string this[string index] =>
197
        _dictionary.TryGetValue(index, out var value) ? value : null; // Indexer declaration
502✔
198

199
    public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => _dictionary.GetEnumerator();
8✔
200

201
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
8✔
202

203
    /// <summary>
204
    /// Returns documentation for the class specified up to maxLength characters (after which ... is appended).  Returns null if no documentation exists for the class
205
    /// </summary>
206
    /// <param name="maxLength"></param>
207
    /// <param name="type"></param>
208
    /// <param name="allowInterfaceInstead">If no docs are found for Type X then look for IX too</param>
209
    /// <param name="formatAsParagraphs"></param>
210
    /// <returns></returns>
211
    public string GetTypeDocumentationIfExists(int maxLength, Type type, bool allowInterfaceInstead = true,
212
        bool formatAsParagraphs = false)
213
    {
214
        var docs = this[type.Name];
170✔
215

216
        //if it's a generic try looking for an non generic or abstract base etc
217
        if (docs == null && type.Name.EndsWith("`1"))
170!
218
            docs = this[type.Name[..^"`1".Length]];
×
219

220
        if (docs == null && allowInterfaceInstead && !type.IsInterface)
170✔
221
            docs = this[$"I{type.Name}"];
46✔
222

223
        if (string.IsNullOrWhiteSpace(docs))
170✔
224
            return null;
8✔
225

226
        if (formatAsParagraphs)
162✔
227
            docs = FormatAsParagraphs(docs);
156✔
228

229
        maxLength = Math.Max(10, maxLength - 3);
162✔
230

231
        return docs.Length <= maxLength ? docs : $"{docs[..maxLength]}...";
162!
232
    }
233

234
    /// <inheritdoc cref="GetTypeDocumentationIfExists(int,Type,bool,bool)"/>
235
    public string GetTypeDocumentationIfExists(Type type, bool allowInterfaceInstead = true,
236
        bool formatAsParagraphs = false) =>
237
        GetTypeDocumentationIfExists(int.MaxValue, type, allowInterfaceInstead, formatAsParagraphs);
170✔
238

239
    /// <summary>
240
    /// Searches the CommentStore for variations of the <paramref name="word"/> and returns the documentation if found (or null)
241
    /// </summary>
242
    /// <param name="word"></param>
243
    /// <param name="fuzzyMatch"></param>
244
    /// <param name="formatAsParagraphs">true to pass result string through <see cref="FormatAsParagraphs"/></param>
245
    /// <returns></returns>
246
    public string GetDocumentationIfExists(string word, bool fuzzyMatch, bool formatAsParagraphs = false)
247
    {
248
        var match = GetDocumentationKeywordIfExists(word, fuzzyMatch);
76✔
249

250
        return match == null ? null : formatAsParagraphs ? FormatAsParagraphs(this[match]) : this[match];
76!
251
    }
252

253
    /// <summary>
254
    /// Searches the CommentStore for variations of the <paramref name="word"/> and returns the key that matches (which might be word verbatim).
255
    /// 
256
    /// <para>This does not return the actual documentation, use <see cref="GetDocumentationIfExists"/> for that</para>
257
    /// </summary>
258
    /// <param name="word"></param>
259
    /// <param name="fuzzyMatch"></param>
260
    /// <returns></returns>
261
    public string GetDocumentationKeywordIfExists(string word, bool fuzzyMatch)
262
    {
263
        if (ContainsKey(word)) return word;
150✔
264
        if (!fuzzyMatch) return null;
2!
265

266
        //try the singular if we didn't match the plural
267
        if (word.EndsWith("s"))
2!
268
        {
269
            word = word.TrimEnd('s');
×
270
            if (ContainsKey(word)) return word;
×
271
        }
272

273
        word = $"I{word}";
2✔
274
        return ContainsKey(word) ? word : null;
2!
275
    }
276

277
    /// <summary>
278
    /// Formats a string read from xmldoc into paragraphs and gets rid of namespace prefixes introduced by cref="" notation.
279
    /// </summary>
280
    /// <param name="message"></param>
281
    /// <returns></returns>
282
    public static string FormatAsParagraphs(string message)
283
    {
284
        message = Regex.Replace(message, $"{Environment.NewLine}\\s*", Environment.NewLine + Environment.NewLine);
230✔
285
        message = Regex.Replace(message, @"(\.?[A-z]{2,}\.)+([A-z]+)", m => m.Groups[2].Value);
310✔
286

287
        return message;
230✔
288
    }
289
}
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