Skip to content

Commit

Permalink
fix: switch to class PairType
Browse files Browse the repository at this point in the history
> replace `Key` & `Value` record with subclasses of `PairType`
> add package Equatable to ease equality comparison. Poor hashing led to test failures despite objects being same.
> refactor `NodeData` to use new pair types
> clean up code references using old `PairType` record
  • Loading branch information
kekavc24 committed Dec 23, 2023
1 parent 93c15b2 commit 4af6107
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ base class ConsolePrinter {
}.toList();

// Wrap with ansicodes
final pathToTrack = wrapMatches(path: data.getPath(), matches: matches);
final pathToTrack = wrapMatches(path: data.toString(), matches: matches);
final trackedPath = TrackedValue(key: pathToTrack, origin: Origin.custom);

// Get all keys that where found at this path
Expand Down
43 changes: 26 additions & 17 deletions lib/src/core/yaml_transformers/data/matched_node_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,35 @@ class MatchedNodeData extends NodeData {

/// Get path of keys upto the last renameable key
String getPathToLastKey() {
return getUptoLastRenameable().map((e) => e.value!).join('/');
return getUptoLastRenameable().map((e) => e.toString()).join('/');
}

///
@override
bool operator ==(Object other) =>
other is MatchedNodeData &&
super == other &&
collectionsUnorderedMatch(matchedKeys, other.matchedKeys) &&
matchedValue == other.matchedValue &&
collectionsUnorderedMatch(matchedPairs, other.matchedPairs);

///
@override
int get hashCode => Object.hashAll([
super.precedingKeys,
super.key,
super.value,
List<Object> get props => [
...super.props,
matchedKeys,
matchedValue,
matchedPairs,
]);
matchedValue,
];

// @override
// bool operator ==(Object other) =>
// other is MatchedNodeData &&
// super == other &&
// collectionsUnorderedMatch(matchedKeys, other.matchedKeys) &&
// matchedValue == other.matchedValue &&
// collectionsUnorderedMatch(matchedPairs, other.matchedPairs);

// @override
// int get hashCode {
// return Object.hashAll([
// super.precedingKeys,
// super.key,
// super.value,
// matchedKeys,
// matchedValue,
// matchedPairs,
// ]);
// }
}
99 changes: 38 additions & 61 deletions lib/src/core/yaml_transformers/data/node_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,50 @@ part of '../yaml_transformer.dart';
/// Typically denotes a terminal node found while indexing a Yaml map or any
/// map
@immutable
class NodeData {
class NodeData extends Equatable {
const NodeData(this.precedingKeys, this.key, this.value);

/// Create with default constructor
factory NodeData.skeleton({
const NodeData.skeleton({
required List<Key> precedingKeys,
required Key key,
required Value value,
}) {
return NodeData(precedingKeys, key, value);
}
}) : this(precedingKeys, key, value);

/// Create using List<String> path and key
factory NodeData.stringSkeleton({
NodeData.stringSkeleton({
required List<String> path,
required String key,
required String value,
}) {
return NodeData.skeleton(
precedingKeys: path.map((e) => createKey(value: e)).toList(),
key: createKey(value: key),
value: createValue(value: value),
);
}
}) : this.skeleton(
precedingKeys: path.map((e) => createPair<Key>(value: e)).toList(),
key: createPair<Key>(value: key),
value: createPair<Value>(value: value),
);

/// Create from the root anchor key
factory NodeData.fromRoot({required String key, required dynamic value}) {
return NodeData(
const [],
createKey(value: key),
createValue(value: value),
);
}
NodeData.fromRoot({required dynamic key, required dynamic value})
: this.skeleton(
precedingKeys: const [],
key: createPair<Key>(value: key),
value: createPair<Value>(value: value),
);

/// Creates from entry in map.
///
/// * By default, the level & index will apply to the key itself rather than
/// the value.
/// * This is because we recursed the list to reach this key rather than the
/// value.
factory NodeData.fromMapEntry({
NodeData.fromMapEntry({
required NodeData parent,
required MapEntry<dynamic, dynamic> current,
required List<int> indices,
}) {
return NodeData(
[...parent.precedingKeys, parent.key],
createKey(value: current.key as String?, indices: indices),
createValue(value: current.value),
);
}
}) : this.skeleton(
precedingKeys: [...parent.precedingKeys, parent.key],
key: createPair<Key>(value: current.key, indices: indices),
value: createPair<Value>(value: current.value),
);

/// Creates from terminal value at the end of a node
///
Expand All @@ -65,17 +58,15 @@ class NodeData {
///
/// * The parent's key will be this terminal value's key too as it's the
/// nearest key linking this value to a map.
factory NodeData.atRootTerminal({
NodeData.atRootTerminal({
required NodeData parent,
required String terminalValue,
required dynamic terminalValue,
required List<int> indices,
}) {
return NodeData(
[...parent.precedingKeys],
parent.key,
createValue(value: terminalValue, indices: indices),
);
}
}) : this.skeleton(
precedingKeys: [...parent.precedingKeys],
key: parent.key,
value: createPair<Value>(value: terminalValue, indices: indices),
);

/// Any preceding keys for this node
final List<Key> precedingKeys;
Expand All @@ -86,21 +77,18 @@ class NodeData {
/// Current data at this node
final Value value;

/// Gets the actual value rather than the object storing it i.e.
///
/// ```dart
/// Value.value
/// ```
dynamic get data => value.value;
/// Gets the actual value at terminal end as a string. A null value will be
/// returned as 'null'.
String get data => value.toString();

/// Transform to key value pairs, based on this node data's path.
///
/// Note: the terminal value must be a string
Map<String, String?> transformToPairs() {
Map<String, String> transformToPairs() {
// Get length of list
final lastIndex = precedingKeys.length - 1;

final mapOfPairs = <String, String?>{};
final mapOfPairs = <String, String>{};

// Loop all and create keys in tandem
for (final (index, candidate) in precedingKeys.indexed) {
Expand All @@ -117,7 +105,7 @@ class NodeData {
}

// Add key and value as last pair
mapOfPairs.addAll({key.toString(): data as String?});
mapOfPairs.addAll({key.toString(): data});
return mapOfPairs;
}

Expand All @@ -131,34 +119,23 @@ class NodeData {

/// Obtains the keys as string. Ignores any indices present
List<String> getKeysAsString() {
return getKeys().map((e) => e.value!).toList();
return getKeys().map((e) => e.toString()).toList();
}

/// Get key path for this node
String getKeyPath() {
return getKeysAsString().join('/');
}

/// Get full path to terminal value
String getPath() {
return "${getKeyPath()}${(data == null) ? '' : '/$data'}";
}

@override
bool operator ==(Object other) =>
other is NodeData &&
other.key == key &&
other.value == value &&
collectionsMatch(precedingKeys, other.precedingKeys);

@override
int get hashCode => Object.hashAll([key, value, precedingKeys]);
List<Object> get props => [precedingKeys, key, value];

/// Obtains full path to terminal value of this node
@override
String toString() => '${getKeyPath()}/$data';

/// Checks whether this node is nested
bool isNested() =>
bool isNestedInList() =>
key.isNested() ||
value.isNested() ||
precedingKeys.isNotEmpty &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:magical_version_bump/src/utils/extensions/map_extensions.dart';
import 'package:magical_version_bump/src/utils/typedefs/typedefs.dart';
import 'package:meta/meta.dart';

part 'pair_subtypes.dart';

/// Custom key/value definition
@immutable
abstract base class PairType {
PairType({required dynamic value, List<int>? indices})
: _value = value,
_indices = indices ?? [];

/// Indicates the actual value
final dynamic _value;

/// Indicates the list of indices when nested in 1 or more lists
final List<int> _indices;

/// Obtains the actual value stored here at runtime.
dynamic get rawValue => _value;

/// Obtains the list of indices when nested in 1 or more lists
List<int> get indices => _indices;

@override
bool operator ==(Object other) {
return other is PairType &&
_hasSameValue(other.rawValue) &&
collectionsMatch(indices, other.indices);
}

bool _hasSameValue(dynamic other) {
if (rawValue.runtimeType != other.runtimeType) return false;
if (isTerminal(rawValue)) return rawValue == other;
return collectionsMatch(rawValue, other);
}

@override
int get hashCode => Object.hashAll([rawValue, indices]);

/// Returns the value stored at this node as a string
@override
String toString() {
return _value.toString();
}

/// Checks if nested in a list
bool isNested() => _indices.isNotEmpty;
}

/// Creates desired pair type on the fly.
T createPair<T extends PairType>({
required dynamic value,
List<int>? indices,
}) {
if (T == Key) return Key(value: value, indices: indices) as T;
return Value(value: value, indices: indices) as T;
}

/// Creates a list of desired pairs.
List<T> createListOfPair<T extends PairType>({
required List<dynamic> values,
required Map<String, List<int>> indices,
}) {
return values
.map((value) => createPair<T>(value: value, indices: indices[value]))
.toList();
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
part of 'custom_pair_type.dart';

/// A key in a custom key/value definition
final class Key extends PairType {
Key({required super.value, required super.indices})
: assert(
isTerminal(value),
'A type of ${value.runtimeType} cannot be a key',
);
}

/// A value in a custom key/value definition
final class Value extends PairType {
Value({required super.value, required super.indices});
}
Loading

0 comments on commit 4af6107

Please sign in to comment.