Nuke.Cola
Loading...
Searching...
No Matches
XRepoItem.cs
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq;
5using System.Text.RegularExpressions;
6using System.Threading.Tasks;
7using Nuke.Common.Tooling;
8using Nuke.Common.Utilities;
9using Nuke.Common.Utilities.Collections;
10
11namespace Nuke.Cola.Tooling;
12
13/// <summary>
14/// A structured representation of the data `xrepo info` prints out about a package.
15/// `xrepo info` uses a bespoke format which expresses data relationship with indentation. One item
16/// can have multiple items associated with it if they're indented further inside. It's almost like
17/// YAML but it differs ever so slightly. Items can have a key optionally if they have : after a
18/// single first word these are named items and accessible with a string indexer. Value only items
19/// are accessible only through an integer indexer. All items are iterated upon via the IEnumerable
20/// interface.
21/// </summary>
22public class XRepoItem : IEnumerable<XRepoItem>
23{
24 /// <summary>
25 /// Kind of the info item represented
26 /// </summary>
27 public enum Kind
28 {
29 /// <summary>
30 /// The main item containing all other items output by `xrepo info`.
31 /// It has no key and no value
32 /// </summary>
33 Root,
34
35 /// <summary>
36 /// Represents data abpuut a package (require(myPackage)). It has Key only.
37 /// </summary>
38 Package,
39
40 /// <summary>
41 /// An item which has only a key and no value ( -> mykey:)
42 /// </summary>
43 Key,
44
45 /// <summary>
46 /// An item which has both a key and a value ( -> mykey: foobar)
47 /// </summary>
49
50 /// <summary>
51 /// An item which doesn't have a key associated usually a list of stuff ( -> foobar)
52 /// </summary>
53 Value,
54
55 /// <summary>
56 /// If for any cursed reason an item couldn't be parsed while it was passing the IsItemLine
57 /// check. This indicates a bug in the code.
58 /// </summary>
60 }
61
62 /// <summary>
63 /// To help distinguish items from each other
64 /// </summary>
65 public required Kind ItemKind { init; get; }
66
67 /// <summary>
68 /// This item specifies a value which is arbitrary text either after `key:` or the entire line if
69 /// key was not present.
70 /// </summary>
71 public string? Value { init; get; }
72
73 /// <summary>
74 /// This item specifies a key which is a short identifier at the beginning of the line like `mykey:`
75 /// </summary>
76 /// <value></value>
77 public string? Key { init; get; }
78
79 private List<XRepoItem> _unnamedItems = [];
80 private Dictionary<string, XRepoItem> _namedItems = [];
81
82 public IEnumerator<XRepoItem> GetEnumerator()
83 {
84 foreach (var item in _unnamedItems) yield return item;
85 foreach (var item in _namedItems.Values) yield return item;
86 }
87
88 IEnumerator IEnumerable.GetEnumerator()
89 {
90 foreach (var item in _unnamedItems) yield return item;
91 foreach (var item in _namedItems.Values) yield return item;
92 }
93
94 /// <summary>
95 /// Get one unnamed sub-item, return null if out-of-bounds
96 /// </summary>
97 public XRepoItem? this[int i] => i >= 0 && i < _unnamedItems.Count ? _unnamedItems[i] : null;
98
99 /// <summary>
100 /// Get one named sub-item, return null if doesn't exist
101 /// </summary>
102 public XRepoItem? this[string i] => _namedItems.TryGetValue(i, out var output) ? output : null;
103
104 private const int MinimumIndent = 4;
105
106 private static bool IsItemLine(string line)
107 => !string.IsNullOrWhiteSpace(line)
108 && line.StartsWith(new string(' ', MinimumIndent))
109 && line.TrimStart().StartsWithAny("require", "->");
110
111 private static bool IsWithinCurrentItem(string line, int indent)
112 => !IsItemLine(line) || GetIndent(line) > indent;
113
114 private static int GetIndent(string line) => line.TakeWhile(c => c == ' ').Count();
115
116 private static XRepoItem Parse(ref List<string> infoOutput, ref int i)
117 {
118 var line = infoOutput[i];
119 int indent = GetIndent(line);
120 var options = RegexOptions.IgnoreCase;
121 // TODO: compile regex patterns in compile time
122 var packKey = line.Parse(@"\srequire\‍((?<KEY>[a-z].*)\‍)\:", options, forceNullOnWhitespce: true)("KEY");
123 var keyOnly = line.Parse(@"\s->\s(?<KEY>[a-z]\w*)\:$", options, forceNullOnWhitespce: true)("KEY");
124 var propWithValue = line.Parse(@"\s->\s(?:(?<KEY>[a-z]\w*)\:\s)?(?<VALUE>.+)$", options, forceNullOnWhitespce: true);
125
126 Kind kind = packKey != null ? Kind.Package
127 : keyOnly != null ? Kind.Key
128 : propWithValue("KEY") != null && propWithValue("VALUE") != null ? Kind.Property
129 : propWithValue("VALUE") != null ? Kind.Value
130 : Kind.Invalid;
131
132 var key = kind switch {
133 Kind.Package => packKey,
134 Kind.Key => keyOnly,
135 Kind.Property => propWithValue("KEY"),
136 _ => null
137 };
138
139 var value = kind switch {
140 Kind.Property or Kind.Value => propWithValue("VALUE"),
141 _ => null
142 };
143
144 var result = new XRepoItem { ItemKind = kind, Key = key, Value = value };
145
146 i++;
147 while(i < infoOutput.Count)
148 {
149 line = infoOutput[i];
150 if (!IsWithinCurrentItem(line, indent)) break;
151 if (!IsItemLine(line))
152 {
153 i++;
154 continue;
155 }
156 var item = Parse(ref infoOutput, ref i);
157 if (item.Key == null)
158 result._unnamedItems.Add(item);
159 else
160 result._namedItems.Add(item.Key!, item);
161 }
162
163 return result;
164 }
165
166 /// <summary>
167 /// Parse the xrepo info structure from a Tool output
168 /// </summary>
169 internal static XRepoItem Parse(IEnumerable<Output> toolOutput)
170 {
171 var infoOutput = toolOutput
172 .Where(o => o.Type == OutputType.Std)
173 .Select(o => o.Text.TrimEnd())
174 .ToList();
175 var result = new XRepoItem { ItemKind = Kind.Root };
176 int i = infoOutput.FindIndex(0, IsItemLine);
177
178 string line = "";
179 while(i < infoOutput.Count)
180 {
181 line = infoOutput[i];
182 if (!IsItemLine(line))
183 {
184 i++;
185 continue;
186 }
187 var item = Parse(ref infoOutput, ref i);
188 if (item.Key == null)
189 result._unnamedItems.Add(item);
190 else
191 result._namedItems.Add(item.Key!, item);
192 }
193 return result;
194 }
195}
A structured representation of the data xrepo info prints out about a package. xrepo info uses a besp...
Definition XRepoItem.cs:23
Kind
Kind of the info item represented.
Definition XRepoItem.cs:28
@ Package
Represents data abpuut a package (require(myPackage)). It has Key only.
@ Invalid
If for any cursed reason an item couldn't be parsed while it was passing the IsItemLine check....
@ Property
An item which has both a key and a value ( -> mykey: foobar)
@ Root
The main item containing all other items output by xrepo info. It has no key and no value.
string? Key
This item specifies a key which is a short identifier at the beginning of the line like mykey:
Definition XRepoItem.cs:77
static XRepoItem Parse(IEnumerable< Output > toolOutput)
Parse the xrepo info structure from a Tool output.
Definition XRepoItem.cs:169
string? Value
This item specifies a value which is arbitrary text either after key: or the entire line if key was n...
Definition XRepoItem.cs:71
required Kind ItemKind
To help distinguish items from each other.
Definition XRepoItem.cs:65