Preserve the YAML sorting order

Also refactor this a bit so Note contains a NoteData. I'm really not
convinced about today's abstractions, and feel I might have made this
entire thing much much worse.
This commit is contained in:
Vishesh Handa
2019-09-26 18:52:01 +02:00
parent ead10ae087
commit 475251df61
8 changed files with 57 additions and 44 deletions

View File

@ -13,18 +13,17 @@ enum NoteLoadState {
class Note implements Comparable<Note> { class Note implements Comparable<Note> {
String filePath = ""; String filePath = "";
DateTime created; DateTime created;
String body = ""; NoteData data = NoteData();
var _loadState = NoteLoadState.None; var _loadState = NoteLoadState.None;
var _serializer = MarkdownYAMLSerializer(); var _serializer = MarkdownYAMLSerializer();
// FIXME: Make it an ordered Map Note([this.filePath]) {
Map<String, dynamic> props = {};
Note({this.created, this.body, this.filePath, this.props}) {
created = created ?? DateTime(0, 0, 0, 0, 0, 0, 0, 0); created = created ?? DateTime(0, 0, 0, 0, 0, 0, 0, 0);
props = props ?? <String, dynamic>{}; }
body = body ?? "";
String get body {
return data.body;
} }
bool hasValidDate() { bool hasValidDate() {
@ -45,15 +44,12 @@ class Note implements Comparable<Note> {
} }
final string = await file.readAsString(); final string = await file.readAsString();
var noteData = _serializer.decode(string); data = _serializer.decode(string);
body = noteData.body; if (data.props.containsKey("created")) {
props = noteData.props; var createdStr = data.props['created'].toString();
if (props.containsKey("created")) {
var createdStr = props['created'].toString();
try { try {
created = DateTime.parse(props['created']).toLocal(); created = DateTime.parse(data.props['created']).toLocal();
} catch (ex) { } catch (ex) {
// Ignore it // Ignore it
} }
@ -80,13 +76,16 @@ class Note implements Comparable<Note> {
// FIXME: What about error handling? // FIXME: What about error handling?
Future<void> save() async { Future<void> save() async {
assert(filePath != null); assert(filePath != null);
assert(data != null);
assert(data.body != null);
assert(data.props != null);
if (hasValidDate()) { if (hasValidDate()) {
props['created'] = toIso8601WithTimezone(created); data.props['created'] = toIso8601WithTimezone(created);
} }
var file = File(filePath); var file = File(filePath);
var contents = _serializer.encode(NoteData(body, props)); var contents = _serializer.encode(data);
await file.writeAsString(contents); await file.writeAsString(contents);
} }
@ -98,8 +97,7 @@ class Note implements Comparable<Note> {
// FIXME: Can't this part be auto-generated? // FIXME: Can't this part be auto-generated?
@override @override
int get hashCode => int get hashCode => filePath.hashCode ^ created.hashCode ^ data.hashCode;
filePath.hashCode ^ created.hashCode ^ body.hashCode ^ props.hashCode;
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@ -107,19 +105,11 @@ class Note implements Comparable<Note> {
other is Note && other is Note &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
filePath == other.filePath && filePath == other.filePath &&
body == other.body && data == other.data;
created == other.created &&
_equalMaps(props, other.props);
static bool _equalMaps(Map a, Map b) {
if (a.length != b.length) return false;
return a.keys
.every((dynamic key) => b.containsKey(key) && a[key] == b[key]);
}
@override @override
String toString() { String toString() {
return 'Note{filePath: $filePath, body: $body, created: $created, props: $props}'; return 'Note{filePath: $filePath, created: $created, data: $data}';
} }
@override @override

View File

@ -57,7 +57,7 @@ class NoteEditorState extends State<NoteEditor> {
icon: Icon(Icons.check), icon: Icon(Icons.check),
onPressed: () { onPressed: () {
final stateContainer = StateContainer.of(context); final stateContainer = StateContainer.of(context);
note.body = _textController.text; note.data.body = _textController.text;
if (note.body.isNotEmpty) { if (note.body.isNotEmpty) {
newNote newNote
? stateContainer.addNote(note) ? stateContainer.addNote(note)

View File

@ -84,7 +84,7 @@ class GitNoteRepository {
var notes = <Note>[]; var notes = <Note>[];
var lister = dir.list(recursive: false); var lister = dir.list(recursive: false);
await for (var fileEntity in lister) { await for (var fileEntity in lister) {
var note = Note(filePath: fileEntity.path); var note = Note(fileEntity.path);
if (!note.filePath.toLowerCase().endsWith('.md')) { if (!note.filePath.toLowerCase().endsWith('.md')) {
continue; continue;
} }

View File

@ -1,11 +1,17 @@
import 'dart:collection';
import 'package:fimber/fimber.dart'; import 'package:fimber/fimber.dart';
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
class NoteData { class NoteData {
String body; String body = "";
Map<String, dynamic> props = {}; LinkedHashMap<String, dynamic> props = LinkedHashMap<String, dynamic>();
NoteData(this.body, this.props); NoteData([this.body, this.props]) {
body = body ?? "";
// ignore: prefer_collection_literals
props = props ?? LinkedHashMap<String, dynamic>();
}
@override @override
int get hashCode => body.hashCode ^ props.hashCode; int get hashCode => body.hashCode ^ props.hashCode;
@ -23,6 +29,11 @@ class NoteData {
return a.keys return a.keys
.every((dynamic key) => b.containsKey(key) && a[key] == b[key]); .every((dynamic key) => b.containsKey(key) && a[key] == b[key]);
} }
@override
String toString() {
return 'NoteData{bodt: $body, props: $props}';
}
} }
abstract class NoteSerializer { abstract class NoteSerializer {
@ -54,7 +65,7 @@ class MarkdownYAMLSerializer implements NoteSerializer {
return NoteData(body, map); return NoteData(body, map);
} }
return NoteData(str, <String, dynamic>{}); return NoteData(str, LinkedHashMap<String, dynamic>());
} }
@override @override

View File

@ -578,7 +578,7 @@ packages:
name: yaml name: yaml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.16" version: "2.2.0"
sdks: sdks:
dart: ">=2.4.0 <3.0.0" dart: ">=2.4.0 <3.0.0"
flutter: ">=1.6.0 <2.0.0" flutter: ">=1.6.0 <2.0.0"

View File

@ -8,7 +8,7 @@ dependencies:
intl: "^0.15.6" intl: "^0.15.6"
path: ^1.6.2 path: ^1.6.2
uuid: ^2.0.1 uuid: ^2.0.1
yaml: ^2.1.15 yaml: ^2.2.0
firebase_analytics: ^4.0.2 firebase_analytics: ^4.0.2
flutter_crashlytics: ^1.0.0 flutter_crashlytics: ^1.0.0
shared_preferences: ^0.5.2 shared_preferences: ^0.5.2

View File

@ -1,6 +1,9 @@
import 'dart:collection';
import 'dart:io'; import 'dart:io';
import 'package:journal/datetime_utils.dart';
import 'package:journal/note.dart'; import 'package:journal/note.dart';
import 'package:journal/storage/serializers.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:test/test.dart'; import 'package:test/test.dart';
@ -15,12 +18,20 @@ void main() {
tempDir = await Directory.systemTemp.createTemp('__storage_test__'); tempDir = await Directory.systemTemp.createTemp('__storage_test__');
var dt = DateTime(2019, 12, 2, 5, 4, 2); var dt = DateTime(2019, 12, 2, 5, 4, 2);
// ignore: prefer_collection_literals
var props = LinkedHashMap<String, dynamic>();
props['created'] = toIso8601WithTimezone(dt);
n1Path = p.join(tempDir.path, "1.md"); n1Path = p.join(tempDir.path, "1.md");
n2Path = p.join(tempDir.path, "2.md"); n2Path = p.join(tempDir.path, "2.md");
notes = <Note>[
Note(filePath: n1Path, body: "test", created: dt), var n1 = Note(n1Path);
Note(filePath: n2Path, body: "test2", created: dt), n1.data = NoteData("test", props);
];
var n2 = Note(n2Path);
n2.data = NoteData("test2", props);
notes = [n1, n2];
}); });
tearDownAll(() async { tearDownAll(() async {
@ -37,7 +48,7 @@ void main() {
var loadedNotes = <Note>[]; var loadedNotes = <Note>[];
await Future.forEach(notes, (origNote) async { await Future.forEach(notes, (origNote) async {
var note = Note(filePath: origNote.filePath); var note = Note(origNote.filePath);
var r = await note.load(); var r = await note.load();
expect(r, NoteLoadState.Loaded); expect(r, NoteLoadState.Loaded);

View File

@ -1,3 +1,5 @@
import 'dart:collection';
import 'package:journal/storage/serializers.dart'; import 'package:journal/storage/serializers.dart';
import 'package:journal/datetime_utils.dart'; import 'package:journal/datetime_utils.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@ -10,7 +12,8 @@ DateTime nowWithoutMicro() {
void main() { void main() {
group('Serializers', () { group('Serializers', () {
var created = toIso8601WithTimezone(nowWithoutMicro()); var created = toIso8601WithTimezone(nowWithoutMicro());
var note = NoteData("This is the body", {"created": created}); var note =
NoteData("This is the body", LinkedHashMap.from({"created": created}));
test('Markdown Serializer', () { test('Markdown Serializer', () {
var serializer = MarkdownYAMLSerializer(); var serializer = MarkdownYAMLSerializer();
@ -47,7 +50,6 @@ Alright.""";
expect(actualStr, note.body); expect(actualStr, note.body);
}); });
/*
test('Markdown Serializer YAML Order', () { test('Markdown Serializer YAML Order', () {
var str = """--- var str = """---
type: Journal type: Journal
@ -63,6 +65,5 @@ Alright.""";
expect(actualStr, str); expect(actualStr, str);
}); });
*/
}); });
} }