Skip to content

Commit

Permalink
feat:
Browse files Browse the repository at this point in the history
* add `keyPartsToString` and `keyPartsFromString`
* add helper `CvModel.valueAtPath(path)` to get a model value at a given path.
  • Loading branch information
alextekartik committed Sep 19, 2024
1 parent 24a083d commit d438679
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 61 deletions.
8 changes: 5 additions & 3 deletions packages/cv/lib/cv.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ export 'src/cv_model.dart'
cvTypeNewModel,
cvNewModel;
export 'src/cv_model_list.dart'
show CvModelListExt, cvNewModelList, cvTypeNewModelList;
export 'src/cv_model_mixin.dart' show CvModelWriteExt, CvModelReadExt;
show CvModelReadListExt, cvNewModelList, cvTypeNewModelList;
export 'src/cv_model_mixin.dart'
show CvModelWriteExt, CvModelReadExt, CvModelCloneExt;
export 'src/cv_tree_path.dart'
show
CvTreePath,
Expand All @@ -57,7 +58,8 @@ export 'src/cv_tree_path.dart'
CvTreePathModelListFieldExt,
CvTreePathModelMapField;
export 'src/list_ext.dart' show ModelRawListExt;
export 'src/map_ext.dart' show ModelRawMapExt;
export 'src/map_ext.dart'
show ModelRawMapExt, keyPartsToString, keyPartsFromString;
export 'src/map_list_ext.dart' show ModelListExt;
export 'src/object_ext.dart' show ModelRawObjectExt;
export 'src/typedefs.dart'
Expand Down
11 changes: 4 additions & 7 deletions packages/cv/lib/src/cv_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ abstract class CvModelRead implements CvModelCore {
Model toMap({List<String>? columns, bool includeMissingValue = false});
}

/// Write helper
abstract class CvModelWrite implements CvModelCore {
/// Write helper (implies CvModelCore)
abstract class CvModelWrite implements CvModelRead {
/// Map alias
void fromMap(Map map, {List<String>? columns});

Expand All @@ -42,9 +42,6 @@ abstract class CvModelCore {

/// CvField access
CvField<T>? field<T extends Object?>(String name);

/// Deep CvField access
CvField<T>? fieldAtPath<T extends Object?>(List<Object> paths);
}

/// Modifiable map.
Expand All @@ -59,8 +56,8 @@ abstract class CvMapModel implements CvModel, Model {
}
}

/// Model to access the data
abstract class CvModel implements CvModelRead, CvModelWrite, CvModelCore {}
/// Model to access the data (implies CvModelRead and CvModelCore)
abstract class CvModel implements CvModelWrite {}

/// Empty model.
class CvModelEmpty extends CvModelBase {
Expand Down
4 changes: 3 additions & 1 deletion packages/cv/lib/src/cv_model_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'dart:collection';
import 'package:cv/cv.dart';
import 'package:cv/src/cv_model_mixin.dart';

import 'cv_model.dart';

/// Empty map.
const cvEmptyMapList = <Model>[];

Expand All @@ -15,7 +17,7 @@ List<T> cvTypeNewModelList<T extends CvModel>(Type type, {bool lazy = true}) =>
cvEmptyMapList.cvType(type, lazy: lazy);

/// List<CvModel> convenient extensions.
extension CvModelListExt<T extends CvModel> on List<T> {
extension CvModelReadListExt<T extends CvModelRead> on List<T> {
/// Convert to model list
List<Model> toMapList(
{List<String>? columns, bool includeMissingValue = false}) {
Expand Down
112 changes: 89 additions & 23 deletions packages/cv/lib/src/cv_model_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,70 @@ var debugContent = false; // devWarning(true);

/// Get raw value helper for map and list.
CvField<T>? rawGetFieldAtPath<T extends Object?>(
Object rawValue, List<Object> paths) {
if (paths.isEmpty) {
Object rawValue, List<Object> parts) {
if (parts.isEmpty) {
if (rawValue is CvField<T>) {
return rawValue;
}
return null;
}
if (rawValue is CvModelField) {
return rawValue.v?.fieldAtPath<T>(paths);
return rawValue.v?.fieldAtPath<T>(parts);
} else if (rawValue is CvModelListField) {
return rawValue.v?.fieldAtPath<T>(paths);
return rawValue.v?.fieldAtPath<T>(parts);
} else if (rawValue is CvModel) {
return rawValue.fieldAtPath<T>(paths);
return rawValue.fieldAtPath<T>(parts);
} else if (rawValue is List) {
return rawValue.fieldAtPath<T>(paths);
return rawValue.fieldAtPath<T>(parts);
}
return null;
}

/// ['key1', 'key2', index3, 'key4]
T? _rawListGetValueAtPath<T extends Object?>(
List<Object?> list, List<Object> parts) {
var path = parts.first;
if (path is int && path >= 0 && list.length > path) {
var rawValue = list[path];
if (rawValue != null) {
return rawGetValueAtPath(rawValue, parts.sublist(1));
}
}
return null;
}

/// ['key1', 'key2', index3, 'key4]
T? _rawMapGetValueAtPath<T extends Object?>(
Map<Object?, Object?> map, List<Object> parts) {
var path = parts.first;
var rawValue = map[path];
if (rawValue != null) {
return rawGetValueAtPath(rawValue, parts.sublist(1));
}
return null;
}

/// Get a value at a given path - internal, handle CvField, CvModel (toMap), List<CvModel> (toMapList)
/// other types are returned as is for now (this might change in the future)
T? rawGetValueAtPath<T extends Object?>(Object rawValue, List<Object> parts) {
var value = (rawValue is CvField) ? rawValue.v : rawValue;
if (value == null) {
return null;
} else if (parts.isEmpty) {
if (value is CvModelRead) {
return value.toMap().anyAs<T?>();
} else if (value is List<CvModelRead>) {
return value.toMapList().anyAs<T?>();
} else if (value is CvModelField) {
return value.v?.toMap().anyAs<T?>();
}
return value.anyAs<T?>();
} else if (rawValue is List) {
return _rawListGetValueAtPath(rawValue, parts);
} else if (rawValue is Map) {
return _rawMapGetValueAtPath(rawValue, parts);
} else if (rawValue is CvModel) {
return rawValue.valueAtPath<T>(parts);
}
return null;
}
Expand Down Expand Up @@ -58,19 +107,6 @@ mixin CvModelMixin implements CvModel {
return _cvFieldMap![name]?.cast<T>();
}

@override
CvField<T>? fieldAtPath<T extends Object?>(List<Object> paths) {
var path = paths.first;
if (path is String) {
var rawField = field<Object>(path);
if (rawField?.isNotNull ?? false) {
return rawGetFieldAtPath<T>(rawField!, paths.sublist(1));
}
}

return null;
}

@override
int get hashCode => fields.first.hashCode;

Expand Down Expand Up @@ -201,8 +237,10 @@ mixin CvModelMixin implements CvModel {

void modelToMap(Model model, CvField field) {
dynamic value = field.v;
if (value is List<CvModelCore>) {
value = value.map((e) => (e as CvModelRead).toMap()).toList();
if (value is List<CvModelRead>) {
value = value
.map((e) => e.toMap(includeMissingValue: includeMissingValue))
.toList();
} else if (value is CvModelRead) {
value = value.toMap(includeMissingValue: includeMissingValue);
}
Expand Down Expand Up @@ -311,12 +349,40 @@ extension CvModelWriteExt on CvModelWrite {
}
}

/// Public extension on CvModelRead
extension CvModelReadExt<T extends CvModel> on T {
/// Public extension on CvModelWrite
extension CvModelCloneExt<T extends CvModel> on T {
/// Copy content
T clone() {
var model = cvNewModel<T>();
model.copyFrom(this);
return model;
}
}

/// Public extension on CvModelCore
extension CvModelReadExt on CvModelRead {
/// Deep CvField access
CvField<T>? fieldAtPath<T extends Object?>(List<Object> parts) {
var path = parts.first;
if (path is String) {
var rawField = field<Object>(path);
if (rawField?.isNotNull ?? false) {
return rawGetFieldAtPath<T>(rawField!, parts.sublist(1));
}
}
return null;
}

/// Get a value at a given path
/// fields value is returned. CvModel/List<CvModel> are converted to map/mapList.
T? valueAtPath<T extends Object?>(List<Object> parts) {
var path = parts.first;
if (path is String) {
var rawValue = field<Object>(path)?.value;
if (rawValue != null) {
return rawGetValueAtPath(rawValue, parts.sublist(1));
}
}
return null;
}
}
18 changes: 10 additions & 8 deletions packages/cv/lib/src/list_ext.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,27 @@ import 'map_ext.dart';
/// Convenient extension on Model
extension ModelRawListExt on List {
/// ['key1', 'key2', index3, 'key4]
T? getKeyPathValue<T extends Object?>(List<Object> paths) {
T? getKeyPathValue<T extends Object?>(List<Object> parts) {
Object? rawValue;
var path = paths.first;
if (path is int && length > path) {
rawValue = this[path];
return rawGetKeyPathValue(rawValue, paths.sublist(1));
var path = parts.first;
if (path is int && path >= 0 && path < length) {
rawValue = this[path] as Object?;
if (rawValue != null) {
return rawGetKeyPathValue(rawValue, parts.sublist(1));
}
}

return null;
}

/// Handle [0, 'key2', 4, 'key4] first must be an int
CvField<F>? fieldAtPath<F extends Object?>(List<Object> paths) {
var path = paths.first;
CvField<F>? fieldAtPath<F extends Object?>(List<Object> parts) {
var path = parts.first;
if (path is int && length > path) {
var rawValue = this[path];
// i.e. not null.
if (rawValue is Object) {
return rawGetFieldAtPath<F>(rawValue, paths.sublist(1));
return rawGetFieldAtPath<F>(rawValue, parts.sublist(1));
}
}
return null;
Expand Down
69 changes: 51 additions & 18 deletions packages/cv/lib/src/map_ext.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,53 @@ import 'package:cv/cv.dart';
import 'package:cv/cv.dart' as cvimpl;
import 'package:cv/src/typedefs.dart';

/// Get raw value helper for map and list.
T? rawGetKeyPathValue<T extends Object?>(Object? rawValue, List<Object> paths) {
if (paths.isEmpty) {
if (rawValue is T) {
return rawValue;
}
return null;
String _keyPartToString(Object part) {
if (part is int) {
return part.toString();
}
assert(part is String);
var partText = part as String;

/// Look like an int
var intValue = int.tryParse(partText);
if (intValue != null) {
return '"$partText"';
}

return partText;
}

Object _keyPartFromString(String partText) {
if (partText.startsWith('"') && partText.endsWith('"')) {
return partText.substring(1, partText.length - 1);
}
var intValue = int.tryParse(partText);
if (intValue is int) {
return intValue;
}
return partText;
}

/// Convert ['key1', 'key2', index3, 'key4] to 'key1.key2.index3.key4'
/// string representing are double quoted (i.e. "1")
/// part with a dot are not supported yet...
String keyPartsToString(List<Object> parts) {
return parts.map(_keyPartToString).join('.');
}

/// Convert 'key1.key2.index3.key4' to ['key1', 'key2', index3, 'key4]
List<Object> keyPartsFromString(String key) {
return key.split('.').map(_keyPartFromString).toList();
}

/// Get raw value helper for map and list - internal
T? rawGetKeyPathValue<T extends Object?>(Object rawValue, List<Object> parts) {
if (parts.isEmpty) {
return rawValue.anyAs<T?>();
} else if (rawValue is Map) {
return rawValue.getKeyPathValue<T>(paths);
return rawValue.getKeyPathValue<T>(parts);
} else if (rawValue is List) {
return rawValue.getKeyPathValue<T>(paths);
return rawValue.getKeyPathValue<T>(parts);
}
return null;
}
Expand Down Expand Up @@ -45,16 +81,13 @@ extension ModelRawMapExt on Map {
}

/// ['key1', 'key2', index3, 'key4]
T? getKeyPathValue<T extends Object?>(List<Object> paths) {
Object? rawValue;
var path = paths.first;
for (var entry in entries) {
if (entry.key == path) {
rawValue = entry.value;
return rawGetKeyPathValue(rawValue, paths.sublist(1));
}
T? getKeyPathValue<T extends Object?>(List<Object> parts) {
var path = parts.first;
var rawValue = this[path] as Object?;
if (rawValue == null) {
return null;
}
return null;
return rawGetKeyPathValue(rawValue, parts.sublist(1));
}

/// Get a value expecting a given type
Expand Down
24 changes: 23 additions & 1 deletion packages/cv/test/cv_model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ void main() {
[IntContent()..value.v = 1]);
});

test('fillCvModel', () {
test('fillCvModel/fieldAtPath/valueAtPath', () {
expect((IntContent()..fillModel(CvFillOptions(valueStart: 0))).toMap(),
{'value': 1});
expect(
Expand Down Expand Up @@ -425,10 +425,32 @@ void main() {
});
expect(
allTypes.fieldAtPath(['children', 0, 'child', 'sub'])?.v, 'text_6');
expect(allTypes.valueAtPath(['children', 0, 'child', 'sub']), 'text_6');

expect(allTypes.fieldAtPath(['children', 0, 'child'])?.v,
isA<ChildContent>());
expect(allTypes.valueAtPath(['children', 0, 'child']), {'sub': 'text_6'});

expect(allTypes.fieldAtPath(['modelList']), isA<CvListField>());
expect(allTypes.valueAtPath(['modelList']), [
{'field_1': 14}
]);

expect(allTypes.fieldAtPath(['children']), isA<CvModelListField>());
expect(allTypes.valueAtPath(['children']), [
{
'child': {'sub': 'text_6'}
}
]);

expect(allTypes.fieldAtPath<int>(['children', 0, 'child', 'sub'])?.v,
isNull);
expect(
allTypes.valueAtPath<int>(['children', 0, 'child', 'sub']), isNull);
expect(allTypes.fieldAtPath<String>(['children', 0, 'child', 'sub'])?.v,
'text_6');
expect(allTypes.valueAtPath<String>(['children', 0, 'child', 'sub']),
'text_6');
expect(
allTypes.fieldAtPath<String>(['children', 0, 'child', 'sub_no'])?.v,
isNull);
Expand Down
Loading

0 comments on commit d438679

Please sign in to comment.