|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.IO; |
| 4 | +using System.Linq; |
| 5 | +using System.Text; |
| 6 | +using System.Xml.Linq; |
| 7 | +using net.adamec.dev.markupdoc.Utils; |
| 8 | + |
| 9 | +namespace net.adamec.dev.markupdoc.AddOns.SourceOnlyPackages.Model |
| 10 | +{ |
| 11 | + /// <summary> |
| 12 | + /// Metadata defining the source-only NuGet packages generated from the source code using the customized build process |
| 13 | + /// </summary> |
| 14 | + /// <remarks> |
| 15 | + /// Source-only NuGet packages contain just the source code that is added to the project the package is added to. |
| 16 | + /// The package is created from the (partial) class or classes in the project folder based on the metadata provided as special XML Documentation Comments. |
| 17 | + /// <list type="bullet"> |
| 18 | + /// <item><term><NuProp.Id></NuProp.Id></term><description>package ID (mandatory)</description></item> |
| 19 | + /// <item><term><NuProp.Version></NuProp.Version></term><description>package version base (major.minor.patch) - optional</description></item> |
| 20 | + /// <item><term><NuProp.Description></NuProp.Description></term><description>package description (optional)</description></item> |
| 21 | + /// <item><term><NuProp.Tags></NuProp.Tags></term><description>package tags (optional)</description></item> |
| 22 | + /// <item><term><NuProp.Includes type = "" /></term><description>file includes (optional). If type="Folder", the package will include all compile files in folder, if type="FolderRecursive" the subfolders will be also included</description></item> |
| 23 | + /// <item><term><NuProp.Using id = "" version=""/></term><description>package imports (optional). Version is optional</description></item> |
| 24 | + /// <item><term><NuProp.Needs id="" /></term><description>"external" imports needed (optional) - not included in package, just info when consuming!!!</description></item> |
| 25 | + /// </list> |
| 26 | + /// </remarks> |
| 27 | + public class NuProps |
| 28 | + { |
| 29 | + /// <summary> |
| 30 | + /// Metadata from <NuProp.Using id = "" version=""/> XML documentation comment |
| 31 | + /// </summary> |
| 32 | + public class NuPropUsing |
| 33 | + { |
| 34 | + /// <summary> |
| 35 | + /// Unique ID of the package |
| 36 | + /// </summary> |
| 37 | + public string PackageId { get; } |
| 38 | + /// <summary> |
| 39 | + /// Optional version of the package |
| 40 | + /// </summary> |
| 41 | + public string PackageVersion { get; } |
| 42 | + |
| 43 | + /// <summary> |
| 44 | + /// CTOR |
| 45 | + /// </summary> |
| 46 | + /// <param name="packageId">Unique ID of the package</param> |
| 47 | + /// <param name="packageVersion">Optional version of the package</param> |
| 48 | + public NuPropUsing(string packageId, string packageVersion) |
| 49 | + { |
| 50 | + PackageId = packageId; |
| 51 | + PackageVersion = packageVersion; |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + /// <summary> |
| 56 | + /// Definition of the additional files to include into the source-only package |
| 57 | + /// </summary> |
| 58 | + public enum IncludesTypeEnum |
| 59 | + { |
| 60 | + /// <summary> |
| 61 | + /// Don't include any other files (only the file defining the source-only package will be included) |
| 62 | + /// </summary> |
| 63 | + None, |
| 64 | + /// <summary> |
| 65 | + /// Include all compilation files from the same folder where the file defining the source-only package is stored |
| 66 | + /// </summary> |
| 67 | + Folder, |
| 68 | + /// <summary> |
| 69 | + /// Include all compilation files from the same folder where the file defining the source-only package is stored and the subfolders |
| 70 | + /// </summary> |
| 71 | + FolderRecursive |
| 72 | + } |
| 73 | + |
| 74 | + /// <summary> |
| 75 | + /// Master flag whether the <see cref="NuProps"/> class containts the valid metadata for source-only package |
| 76 | + /// </summary> |
| 77 | + public bool HasNuProps { get; } |
| 78 | + /// <summary> |
| 79 | + /// Full path to the file declaring the source-only package (containing the package metadata as <NuProp.xxxx/> XML documentation comments |
| 80 | + /// </summary> |
| 81 | + public string DeclaringFile { get; } |
| 82 | + /// <summary> |
| 83 | + /// List of all files to be included into the source-only package |
| 84 | + /// </summary> |
| 85 | + public IReadOnlyList<string> PackageFiles { get; } |
| 86 | + /// <summary> |
| 87 | + /// Unique ID of the package |
| 88 | + /// </summary> |
| 89 | + public string PackageId { get; } |
| 90 | + /// <summary> |
| 91 | + /// Optional version of the package. |
| 92 | + /// </summary> |
| 93 | + /// <remarks>If not defined, the custom build process uses the solution version information</remarks> |
| 94 | + public string PackageVersion { get; } |
| 95 | + /// <summary> |
| 96 | + /// Optional description of the package |
| 97 | + /// </summary> |
| 98 | + /// <remarks>If not defined, the custom build process uses the default generic description</remarks> |
| 99 | + public string PackageDescription { get; } |
| 100 | + /// <summary> |
| 101 | + /// Optional package tags divided by space |
| 102 | + /// </summary> |
| 103 | + public string PackageTags { get; } |
| 104 | + /// <summary> |
| 105 | + /// Definition of the additional files to include into the source-only package |
| 106 | + /// </summary> |
| 107 | + public IncludesTypeEnum IncludesType { get; } = IncludesTypeEnum.None; |
| 108 | + |
| 109 | + /// <summary> |
| 110 | + /// List of the dependencied that are to be declared within the package |
| 111 | + /// </summary> |
| 112 | + public IReadOnlyList<NuPropUsing> Usings { get; } |
| 113 | + |
| 114 | + /// <summary> |
| 115 | + /// List of external references (NuGet package dependencies) that are not declared in the package, but the consumer has to include |
| 116 | + /// </summary> |
| 117 | + public IReadOnlyList<string> ExternalReferences { get; } |
| 118 | + |
| 119 | + /// <summary> |
| 120 | + /// CTOR - Checks the compilation file with given <paramref name="fileName"/> for the source-only package metadata. |
| 121 | + /// When the metadata are present and valid, the <see cref="NuProps"/> object is initialized and <see cref="HasNuProps"/> property is set to true |
| 122 | + /// </summary> |
| 123 | + /// <param name="fileName">Full path to the compliation file</param> |
| 124 | + /// <param name="allFiles">List of all files in compilation, used to resolve the includes (Folder, FolderRecursive) when needed</param> |
| 125 | + public NuProps(string fileName, IReadOnlyCollection<string> allFiles) |
| 126 | + { |
| 127 | + if (allFiles == null || allFiles.Count < 1) return; |
| 128 | + if (string.IsNullOrEmpty(fileName) || !File.Exists(fileName)) |
| 129 | + { |
| 130 | + ConsoleUtils.WriteErrWarn($"Initialize NuProps with empty of non existing file {fileName}"); |
| 131 | + return; |
| 132 | + } |
| 133 | + |
| 134 | + //Get the NuProps from XML Documentation Comments <NuProp.xxxx> |
| 135 | + var sourceContent = File.ReadAllText(fileName); |
| 136 | + var sourceLines = sourceContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); |
| 137 | + //Extract all comments |
| 138 | + var stringBuilder = new StringBuilder(); |
| 139 | + foreach (var contentLine in sourceLines) |
| 140 | + { |
| 141 | + var sourceLine = contentLine.Trim(); |
| 142 | + if (sourceLine.StartsWith("///")) |
| 143 | + { |
| 144 | + stringBuilder.AppendLine(sourceLine.Substring(3)); |
| 145 | + } |
| 146 | + } |
| 147 | + //Get all comments in single XML - encapsulate the whole bunch with dummy tag "doc" allowing the XDocument to parse it |
| 148 | + var xmlDocumentationComments = "<doc>" + stringBuilder + "</doc>"; |
| 149 | + |
| 150 | + if (string.IsNullOrEmpty(xmlDocumentationComments)) return; |
| 151 | + var xDoc = XDocument.Parse(xmlDocumentationComments); |
| 152 | + var nuPropElements = xDoc.Descendants() |
| 153 | + .Where(n => n is XElement e && e.Name.LocalName.StartsWith("NuProp.")).ToList(); |
| 154 | + if (nuPropElements.Count <= 0) return; //no NuProps - continue with the next file |
| 155 | + |
| 156 | + //Get package ID |
| 157 | + PackageId = nuPropElements.FirstOrDefault(e => e.Name.LocalName == "NuProp.Id")?.Value.Trim(); |
| 158 | + if (string.IsNullOrEmpty(PackageId)) |
| 159 | + { |
| 160 | + ConsoleUtils.WriteErrWarn($"NuProp.Id not found for {fileName}"); |
| 161 | + return; |
| 162 | + } |
| 163 | + |
| 164 | + //Get package metadata |
| 165 | + PackageVersion = nuPropElements.FirstOrDefault(e => e.Name.LocalName == "NuProp.Version")?.Value.Trim(); |
| 166 | + PackageDescription = nuPropElements.FirstOrDefault(e => e.Name.LocalName == "NuProp.Description")?.Value.Trim(); |
| 167 | + PackageTags = nuPropElements.FirstOrDefault(e => e.Name.LocalName == "NuProp.Tags")?.Value.Trim(); |
| 168 | + |
| 169 | + var nuPropIncludesStr = nuPropElements |
| 170 | + .FirstOrDefault(e => e.Name.LocalName == "NuProp.Includes" && e.Attribute("type")?.Value != null)? |
| 171 | + .Attribute("type")?.Value; |
| 172 | + // ReSharper disable once SwitchStatementMissingSomeCases |
| 173 | + switch (nuPropIncludesStr) |
| 174 | + { |
| 175 | + case "Folder": IncludesType = IncludesTypeEnum.Folder; break; |
| 176 | + case "FolderRecursive": IncludesType = IncludesTypeEnum.FolderRecursive; break; |
| 177 | + } |
| 178 | + |
| 179 | + var nuPropUsings = nuPropElements.Where(e => e.Name.LocalName == "NuProp.Using" && e.Attribute("id")?.Value != null).ToList(); |
| 180 | + if (nuPropUsings.Count > 0) |
| 181 | + { |
| 182 | + var usings = new List<NuPropUsing>(); |
| 183 | + Usings = usings; |
| 184 | + //have some dependencies |
| 185 | + foreach (var nuPropUsing in nuPropUsings) |
| 186 | + { |
| 187 | + // ReSharper disable once PossibleNullReferenceException - should not be null based on Where clause for nuPropUsings |
| 188 | + var depId = nuPropUsing.Attribute("id").Value; |
| 189 | + var depVersion = nuPropUsing.Attribute("version")?.Value; |
| 190 | + usings.Add(new NuPropUsing(depId, depVersion)); |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + var nuPropNeeds = nuPropElements.Where(e => e.Name.LocalName == "NuProp.Needs" && e.Attribute("id")?.Value != null).ToList(); |
| 195 | + if (nuPropNeeds.Count > 0) |
| 196 | + { |
| 197 | + var externalReferences = new List<string>(); |
| 198 | + ExternalReferences = externalReferences; |
| 199 | + //have some dependencies |
| 200 | + foreach (var nuPropNeed in nuPropNeeds) |
| 201 | + { |
| 202 | + // ReSharper disable once PossibleNullReferenceException - should not be null based on Where clause for nuPropNeeds |
| 203 | + externalReferences.Add(nuPropNeed.Attribute("id").Value); |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + //Get all package files (processing the includes when applicable) |
| 208 | + var packageFiles = new List<string>(); |
| 209 | + PackageFiles = packageFiles; |
| 210 | + packageFiles.Add(fileName); |
| 211 | + if (IncludesType != IncludesTypeEnum.None) |
| 212 | + { |
| 213 | + var mainItemDir = new FileInfo(fileName).DirectoryName; |
| 214 | + if (mainItemDir != null) |
| 215 | + { |
| 216 | + // ReSharper disable once SwitchStatementMissingSomeCases |
| 217 | + switch (IncludesType) |
| 218 | + { |
| 219 | + case IncludesTypeEnum.Folder: |
| 220 | + packageFiles.AddRange(allFiles.Where(itm => new FileInfo(itm).DirectoryName == mainItemDir && itm != fileName)); |
| 221 | + break; |
| 222 | + case IncludesTypeEnum.FolderRecursive: |
| 223 | + packageFiles.AddRange(allFiles.Where(itm => itm.StartsWith(mainItemDir) && itm != fileName)); |
| 224 | + break; |
| 225 | + default: |
| 226 | + throw new ArgumentOutOfRangeException(); |
| 227 | + } |
| 228 | + } |
| 229 | + } |
| 230 | + |
| 231 | + DeclaringFile = fileName; |
| 232 | + //It's valid source-only package definition (metadata) |
| 233 | + HasNuProps = true; |
| 234 | + } |
| 235 | + } |
| 236 | +} |
0 commit comments