1+ using ColumnizerLib ;
2+
3+ using LogExpert . Core . Config ;
4+
5+ namespace LogExpert . Core . Classes . Columnizer ;
6+
7+ /// <summary>
8+ /// Pure precedence-chain resolver that picks a columnizer for a given file from up to four sources: per-file
9+ /// persistence, columnizer history, columnizer-mask list, and AutoPick.
10+ /// </summary>
11+ /// <remarks>
12+ /// The exact order of consultation is controlled by <see cref="ColumnizerSelectionPriority"/>. AutoPick fires only when
13+ /// every other source produced <see langword="null"/>; it never outranks an explicit Mask, History, or Persistence hit.
14+ /// <para> Stale Mask entries — those whose <see cref="ColumnizerMaskEntry.ColumnizerName"/> is not registered — are
15+ /// skipped (an optional callback is invoked once per skipped entry) and resolution continues with the next entry in the
16+ /// list. </para>
17+ /// </remarks>
18+ public static class ColumnizerResolver
19+ {
20+ /// <summary>
21+ /// Returns the winning columnizer for the inputs, or <see langword="null"/> if no source produced a match.
22+ /// </summary>
23+ public static ILogLineMemoryColumnizer ? Resolve ( ResolveInputs inputs )
24+ {
25+ ArgumentNullException . ThrowIfNull ( inputs ) ;
26+
27+ // Look up a columnizer by name with "no signal" semantics — returns null when the name is
28+ // missing OR not registered, so the precedence chain falls through to the next source.
29+ ILogLineMemoryColumnizer ? byName ( string ? name ) => string . IsNullOrEmpty ( name ) ? null : ColumnizerPicker . FindMemoryColumnizerByName ( name , inputs . Registered ) ;
30+ ILogLineMemoryColumnizer ? mask ( ) => TryGetMaskColumnizer ( inputs . MaskList , inputs . ShortFileName , inputs . Registered , inputs . OnStaleMaskEntry ) ;
31+ ILogLineMemoryColumnizer ? history ( ) => byName ( inputs . HistoryLookup ? . Invoke ( inputs . FileName ) ) ;
32+ ILogLineMemoryColumnizer ? persistence ( ) => byName ( inputs . PersistenceColumnizerName ) ;
33+
34+ var winner = inputs . Priority switch
35+ {
36+ ColumnizerSelectionPriority . MaskThenHistory => persistence ( ) ?? mask ( ) ?? history ( ) ,
37+ ColumnizerSelectionPriority . MaskOverridesPersistence => mask ( ) ?? persistence ( ) ?? history ( ) ,
38+ ColumnizerSelectionPriority . HistoryThenMask => persistence ( ) ?? history ( ) ?? mask ( ) ,
39+ _ => persistence ( ) ?? history ( ) ?? mask ( ) ,
40+ } ;
41+
42+ return winner ?? inputs . AutoPick ? . Invoke ( ) ;
43+ }
44+
45+ /// <summary>
46+ /// Iterates the mask list and returns the first non-stale match. Entries whose columnizer is not registered invoke
47+ /// <paramref name="onStale"/> and the iteration continues. Never throws.
48+ /// </summary>
49+ public static ILogLineMemoryColumnizer ? TryGetMaskColumnizer (
50+ IReadOnlyList < ColumnizerMaskEntry > maskList ,
51+ string shortFileName ,
52+ IList < ILogLineMemoryColumnizer > registered ,
53+ Action < ColumnizerMaskEntry > ? onStale = null )
54+ {
55+ if ( maskList == null || maskList . Count == 0 || string . IsNullOrEmpty ( shortFileName ) )
56+ {
57+ return null ;
58+ }
59+
60+ foreach ( var entry in maskList )
61+ {
62+ if ( ! ColumnizerMaskMatcher . Matches ( entry , shortFileName ) )
63+ {
64+ continue ;
65+ }
66+
67+
68+ // FindMemoryColumnizerByName returns null when the name isn't registered, which is
69+ // exactly the "stale entry" signal we need.
70+ var columnizer = ColumnizerPicker . FindMemoryColumnizerByName ( entry . ColumnizerName , registered ) ;
71+ if ( columnizer != null )
72+ {
73+ return columnizer ;
74+ }
75+
76+ onStale ? . Invoke ( entry ) ;
77+ }
78+
79+ return null ;
80+ }
81+ }
0 commit comments