-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(initial): add initial manager implementation
> add `FindManager` & `ReplacerManager` for simple aggregation > add `TransformTracker` to managers to abstract aggregation progress
- Loading branch information
Showing
7 changed files
with
681 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
294 changes: 294 additions & 0 deletions
294
lib/src/core/yaml_transformers/managers/finder_manager.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,294 @@ | ||
part of 'manager.dart'; | ||
|
||
enum FinderType { byValue, bySearch, both } | ||
|
||
typedef FindManagerOutput = ({ | ||
int currentFile, | ||
MatchedNodeData data, | ||
bool reachedLimit, | ||
}); | ||
|
||
class FinderManager extends TransformerManager implements ManageByCount { | ||
FinderManager._({ | ||
required super.files, | ||
required super.aggregator, | ||
required KeysToFind keysToFind, | ||
required ValuesToFind valuesToFind, | ||
required PairsToFind pairsToFind, | ||
required FinderType finderType, | ||
}) : _finderType = finderType, | ||
_keysToFind = keysToFind, | ||
_valuesToFind = valuesToFind, | ||
_pairsToFind = pairsToFind; | ||
|
||
factory FinderManager.forFinder({ | ||
required List<FileOutput> files, | ||
required Aggregator aggregator, | ||
required KeysToFind keysToFind, | ||
required ValuesToFind valuesToFind, | ||
required PairsToFind pairsToFind, | ||
FinderType? finderType, | ||
}) { | ||
return FinderManager._( | ||
files: files, | ||
aggregator: aggregator, | ||
finderType: finderType ?? FinderType.byValue, | ||
keysToFind: keysToFind, | ||
valuesToFind: valuesToFind, | ||
pairsToFind: pairsToFind, | ||
); | ||
} | ||
|
||
factory FinderManager.findValues({ | ||
required List<FileOutput> files, | ||
required Aggregator aggregator, | ||
required ValuesToFind valuesToFind, | ||
}) { | ||
return FinderManager._( | ||
files: files, | ||
aggregator: aggregator, | ||
keysToFind: (keys: [], orderType: OrderType.loose), | ||
valuesToFind: valuesToFind, | ||
pairsToFind: {}, | ||
finderType: FinderType.byValue, | ||
); | ||
} | ||
|
||
factory FinderManager.findeKeys({ | ||
required List<FileOutput> files, | ||
required Aggregator aggregator, | ||
required KeysToFind keysToFind, | ||
}) { | ||
return FinderManager._( | ||
files: files, | ||
aggregator: aggregator, | ||
keysToFind: keysToFind, | ||
valuesToFind: [], | ||
pairsToFind: {}, | ||
finderType: FinderType.byValue, | ||
); | ||
} | ||
|
||
late FinderType _finderType; | ||
|
||
late KeysToFind _keysToFind; | ||
late ValuesToFind _valuesToFind; | ||
late PairsToFind _pairsToFind; | ||
|
||
/// Obtains the current generator to be used/ currently in use by | ||
/// this manager | ||
Iterable<FindManagerOutput> getGenerator() { | ||
return aggregator.type == AggregateType.all | ||
? transformAll(resetTracker: true) | ||
: transformByCount( | ||
aggregator.count!, | ||
applyToEach: aggregator.applyToEach, | ||
); | ||
} | ||
|
||
/// Prefills the tracker with keys for accurate value tracking | ||
void _prefillTracker() { | ||
for (final data in keysToPrefill()) { | ||
_tracker.prefill(data.keys, origin: data.origin); | ||
} | ||
} | ||
|
||
@override | ||
Future<void> transform() async { | ||
|
||
// TODO: Add match formatter/aggregator | ||
for (final match in getGenerator()) {} | ||
} | ||
|
||
@override | ||
Iterable<FindManagerOutput> transformByCount( | ||
int count, { | ||
required bool applyToEach, | ||
bool applyToEachFile = true, | ||
}) sync* { | ||
// Prefill tracker | ||
_prefillTracker(); | ||
|
||
/// If we are not applying to each file and neither are we applying to | ||
/// to each argument. Return just count | ||
if (!applyToEachFile && !applyToEach) { | ||
yield* transformAll(resetTracker: true).take(count); | ||
} | ||
|
||
/// | ||
/// For remaining conditions: | ||
/// * `applyToEachFile` && `!applyToEach` - for each file we take | ||
/// the specified count | ||
/// | ||
/// * `!applyToEachFile` && `applyToEach` - we never reset the tracker, | ||
/// and we terminate once all our arguments each reach specified count | ||
/// | ||
/// * `applyToEachFile` && `applyToEach` - each file gets equal chance to | ||
/// reach the number of specified args even if we end up recursing the | ||
/// whole file! | ||
/// | ||
// we never reset the tracker, terminate once arg conditions are met | ||
else if (!applyToEachFile && applyToEach) { | ||
yield* transformAll(resetTracker: false).takeWhile( | ||
(value) => !value.reachedLimit, | ||
); | ||
} | ||
|
||
/// | ||
/// All conditions below fall under `applyToEachFile` | ||
/// | ||
/// When `applyToEachFile` is true, it is imperative to optimize | ||
/// transformations, saving on "time", even some few milliseconds. | ||
/// | ||
/// We interrupt current transformation and skip to next file manually. | ||
/// | ||
else if (applyToEachFile) { | ||
/// When `applyToEach` argument is false, we keep count per file | ||
var countForEachFile = <int, int>{}; | ||
|
||
/// When `applyToEach` argument is true, we track current active file & | ||
/// whether we reach the limit for count for each argument and yielded the | ||
/// last value that triggered the match | ||
var currentFile = 0; | ||
|
||
/// We create our custom queue with all files | ||
var customQueue = QueueList.from(yamlQueue); | ||
|
||
// Setup all our variables for tracking exiting current transformation | ||
if (!applyToEach) { | ||
countForEachFile = yamlQueue.asMap().keys.fold( | ||
<int, int>{}, | ||
(previousValue, element) { | ||
previousValue.addAll({element: 0}); | ||
return previousValue; | ||
}, | ||
); | ||
} | ||
|
||
/// Our queue will act as the reference for controlling the loop | ||
while (customQueue.isNotEmpty) { | ||
final generator = transformAll( | ||
resetTracker: true, | ||
customQueue: customQueue, | ||
); | ||
|
||
// Start transformation | ||
for (final match in generator) { | ||
/// | ||
/// When `applyToEach` argument is false, we break loop if count for | ||
/// matches generated for this file has been reached | ||
if (!applyToEach) { | ||
if (countForEachFile[match.currentFile] == count) break; | ||
|
||
// Increment its count if loop wasn't broken | ||
countForEachFile.update(match.currentFile, (value) => value + 1); | ||
} | ||
|
||
/// | ||
/// When `applyToEach` argument is true, we break loop if current | ||
/// file limit has been reached for all arguments | ||
else { | ||
if (currentFile == match.currentFile && match.reachedLimit) { | ||
// Yield match that triggered limit and break | ||
yield match; | ||
break; | ||
} | ||
} | ||
|
||
/// If generator moved on to another file, update current file. | ||
/// | ||
/// This occurs when the file has not met the threshold for any of | ||
/// the above conditions | ||
/// | ||
/// OR | ||
/// | ||
/// It did, and the generator continued. A generator just generates! | ||
if (currentFile != match.currentFile) { | ||
currentFile = match.currentFile; | ||
} | ||
|
||
// Always yield match | ||
yield match; | ||
} | ||
|
||
/// If loop was terminated, we modify custom queue and skip files | ||
/// that were touched. This given by: | ||
/// | ||
/// currentFile + 1, which will be our start index | ||
/// | ||
/// If by chance the loop just ended, then we'll skip all elements. This | ||
/// renders our custom queue empty thus controller loop will be | ||
/// terminated! | ||
/// | ||
currentFile += 1; | ||
|
||
customQueue = QueueList.from(yamlQueue.skip(currentFile)); | ||
} | ||
} | ||
} | ||
|
||
@override | ||
Iterable<FindManagerOutput> transformAll({ | ||
required bool resetTracker, | ||
QueueList<YamlMap>? customQueue, | ||
}) sync* { | ||
final localQueue = customQueue ?? QueueList.from(yamlQueue); | ||
|
||
do { | ||
// Index of current file | ||
final currentFile = yamlQueue.length - localQueue.length; | ||
|
||
// Reset tracker if not first run, since we havent completed it. | ||
if (localQueue.length != yamlQueue.length && resetTracker) { | ||
super.resetTracker(); | ||
} | ||
|
||
final yamlMap = localQueue.removeFirst(); // Get file, remove from list | ||
|
||
/// If first run, use current yaml map to initialize. | ||
/// | ||
/// New files just set a new yaml indexer | ||
final finder = _setUpFinder(yamlMap); // Get finder | ||
|
||
// Now generate, and append current file number | ||
for (final match in finder.findAllSync()) { | ||
yield ( | ||
currentFile: currentFile, | ||
data: match, | ||
reachedLimit: super.incrementWithMatch(match), | ||
); | ||
} | ||
} while (localQueue.isNotEmpty); | ||
} | ||
|
||
@override | ||
List<PrefillData> keysToPrefill() { | ||
final keys = <PrefillData>[]; | ||
|
||
if (_keysToFind.keys.isNotEmpty) { | ||
keys.add((keys: _keysToFind.keys, origin: Origin.key)); | ||
} | ||
if (_valuesToFind.isNotEmpty) { | ||
keys.add((keys: _valuesToFind, origin: Origin.value)); | ||
} | ||
if (_pairsToFind.isNotEmpty) { | ||
keys.add((keys: _pairsToFind.entries.toList(), origin: null)); | ||
} | ||
|
||
return keys; | ||
} | ||
|
||
/// Get finder for this manager | ||
Finder _setUpFinder(YamlMap yamlMap) { | ||
/// Currently just returns only the [MagicalFinder] for now | ||
return switch (_finderType) { | ||
_ => MagicalFinder.findInYaml( | ||
yamlMap, | ||
keysToFind: _keysToFind, | ||
valuesToFind: _valuesToFind, | ||
pairsToFind: _pairsToFind, | ||
), | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import 'package:collection/collection.dart'; | ||
import 'package:magical_version_bump/src/core/yaml_transformers/managers/tranform_tracker/transform_tracker.dart'; | ||
import 'package:magical_version_bump/src/core/yaml_transformers/yaml_transformer.dart'; | ||
import 'package:magical_version_bump/src/utils/enums/enums.dart'; | ||
import 'package:magical_version_bump/src/utils/typedefs/typedefs.dart'; | ||
import 'package:yaml/yaml.dart'; | ||
|
||
part 'finder_manager.dart'; | ||
part 'replacer_manager.dart'; | ||
|
||
typedef PrefillData = ({List<dynamic> keys, Origin? origin}); | ||
|
||
abstract class TransformerManager { | ||
TransformerManager({ | ||
required List<FileOutput> files, | ||
required Aggregator aggregator, | ||
}) : _aggregator = aggregator, | ||
_yamlQueue = QueueList.from(files.map((e) => e.fileAsMap)), | ||
_tracker = TransformTracker(limit: aggregator.count); | ||
|
||
/// A queue of all yaml maps to run a transform operation on | ||
final QueueList<YamlMap> _yamlQueue; | ||
|
||
/// A custom Aggregator for this transformer | ||
final Aggregator _aggregator; | ||
|
||
/// Tracker for keeping track of transformations made. | ||
final TransformTracker _tracker; | ||
|
||
/// Current queue with yaml files | ||
QueueList<YamlMap> get yamlQueue => _yamlQueue; | ||
|
||
/// Aggregator in use by this manager | ||
Aggregator get aggregator => _aggregator; | ||
|
||
/// Tracker in use by this manager | ||
TransformTracker get tracker => _tracker; | ||
|
||
/// Increments the count of a tracked value being transformed in the | ||
/// tracker using a [ String ] | ||
void incrementWithStrings(List<dynamic> values, {required Origin origin}) { | ||
_tracker.increment(values, origin: origin); | ||
} | ||
|
||
/// Increments the count of a tracked value being transformed in the | ||
/// tracker using a [ MatchedNodeData ] object | ||
/// | ||
/// Return true if limit is reached. | ||
bool incrementWithMatch(MatchedNodeData data) { | ||
return _tracker.incrementUsingMatch(data); | ||
} | ||
|
||
/// Get count of a value in tracker | ||
int getCountOfValue(dynamic value, {required Origin origin}) { | ||
return _tracker.getCount(value, origin: origin); | ||
} | ||
|
||
/// Resets tracker and saves current state to history | ||
void resetTracker() => _tracker.reset(); | ||
|
||
/// Initializes transformer manager | ||
Future<void> transform(); | ||
} | ||
|
||
/// Interface class for implementing by count. This is intended for the | ||
/// [ FinderManager ] that does the heavy lifting (as it should). Finding | ||
/// a match is always the tricky part! | ||
/// | ||
/// This allows for a light & fairly straighforward implementation for | ||
/// [ ReplacerManager ] | ||
abstract interface class ManageByCount { | ||
/// Generates keys to prefill. | ||
/// | ||
/// [ FinderManager ] implements this to preset any values inside the | ||
/// [ TransformTracker ] ensuring we always know the count of each value | ||
/// instead of waiting for it to be added later. | ||
/// | ||
/// Prefilling helps tracker give the accurate status if the count needs to | ||
/// be within the limit. | ||
List<PrefillData> keysToPrefill(); | ||
|
||
/// Transforms based a specified count of requirements. | ||
/// | ||
/// [ count ] - denotes number of values to extract | ||
/// | ||
/// [ applyToEach ] - denotes whether each unique matcher should be | ||
/// transformed by this count | ||
void transformByCount(int count, {required bool applyToEach}); | ||
|
||
/// Transforms all | ||
void transformAll({required bool resetTracker}); | ||
} |
Oops, something went wrong.