Skip to content

Commit

Permalink
Merge v2.0.0 - Rework serialization
Browse files Browse the repository at this point in the history
 - Internals are reworked: now package is relying on a tree-like structure to improve maintainability and scalability.
 - Fixed `indentSize` validation (only values >=1 are allowed).
 - Resolved #6:
 ```yaml
 # BEFORE:
 field:
   - 
     - 5
     - 2
   - 
     a: 3
     b: 6

 # AFTER:
 field:
   - - 5
     - 2
   - a: 3
     b: 6
 ```
 - Update min Dart SDK version constraint from `2.12.0` to `3.0.0`
 - Update `lints: ^2.0.1` to `lints: ^3.0.0`

 BREAKING CHANGES:
 - `YAMLWriter` renamed to `YamlWriter` ([Effective Dart reference](https://dart.dev/effective-dart/style#do-capitalize-acronyms-and-abbreviations-longer-than-two-letters-like-words)).
 - Deprecated `identSize` property is removed. Use `indentSize` instead.
 - `toEncodable` property is removed. Use constructor parameter instead (`YamlWriter#toEncodable`).
  • Loading branch information
gmpassos authored Jan 22, 2024
2 parents bc2499b + 1f7cf2b commit c713cdc
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 258 deletions.
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
## 2.0.0

- Internals are reworked: now package is relying on a tree-like structure to improve maintainability and scalability.
- Fixed `indentSize` validation (only values >=1 are allowed).
- Resolved https://github.com/gmpassos/yaml_writer/issues/6:
```yaml
# BEFORE:
field:
-
- 5
- 2
-
a: 3
b: 6

# AFTER:
field:
- - 5
- 2
- a: 3
b: 6
```
- Update min Dart SDK version constraint from `2.12.0` to `3.0.0`
- Update `lints: ^2.0.1` to `lints: ^3.0.0`

BREAKING CHANGES:
- `YAMLWriter` renamed to `YamlWriter` ([Effective Dart reference](https://dart.dev/effective-dart/style#do-capitalize-acronyms-and-abbreviations-longer-than-two-letters-like-words)).
- Deprecated `identSize` property is removed. Use `indentSize` instead.
- `toEncodable` property is removed. Use constructor parameter instead (`YamlWriter#toEncodable`).


## 1.0.3

- `YAMLWriter`:
Expand Down
2 changes: 1 addition & 1 deletion example/yaml_writer_example.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:yaml_writer/yaml_writer.dart';

void main() {
var yamlWriter = YAMLWriter();
var yamlWriter = YamlWriter();

var yamlDoc = yamlWriter.write({
'name': 'Joe',
Expand Down
156 changes: 156 additions & 0 deletions lib/src/node.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import 'dart:math';

import 'yaml_context.dart';

sealed class Node {
bool get requiresNewLine => false;

List<String> toYaml(YamlContext context);
}

class NullNode extends Node {
@override
List<String> toYaml(YamlContext context) => ['null'];
}

class NumNode extends Node {
final num number;

NumNode(this.number);

@override
List<String> toYaml(YamlContext context) => ['$number'];
}

class BoolNode extends Node {
final bool boolean;

BoolNode(this.boolean);

@override
List<String> toYaml(YamlContext context) => ['$boolean'];
}

class StringNode extends Node {
final String text;

StringNode(this.text);

@override
List<String> toYaml(YamlContext context) {
List<String> yamlLines = [];

if (text.contains('\n')) {
bool endsWithLineBreak = text.endsWith('\n');

List<String> lines;
if (endsWithLineBreak) {
yamlLines.add('|');
lines = text.substring(0, text.length - 1).split('\n');
} else {
yamlLines.add('|-');
lines = text.split('\n');
}

for (int index = 0; index < lines.length; index++) {
yamlLines.add(lines[index]);
}
} else {
var containsSingleQuote = text.contains("'");

if (context.allowUnquotedStrings &&
!containsSingleQuote &&
_isValidUnquotedString(text)) {
yamlLines.add(text);
} else if (!containsSingleQuote) {
yamlLines.add('\'$text\'');
} else {
var str = text.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
yamlLines.add('"$str"');
}
}

return yamlLines;
}

static final _regexpInvalidUnquotedChars = RegExp(
r'[^0-9a-zA-ZàèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ@/. \t-]');

bool _isValidUnquotedString(String s) =>
!_regexpInvalidUnquotedChars.hasMatch(s) &&
!s.startsWith('@') &&
!s.startsWith('-');
}

class ListNode extends Node {
final List<Node> subnodes;

ListNode(this.subnodes);

@override
bool get requiresNewLine => subnodes.isNotEmpty;

@override
List<String> toYaml(YamlContext context) {
if (subnodes.isEmpty) {
return ['[]'];
}

final List<String> lines = [];

for (final node in subnodes) {
final nodeYaml = node.toYaml(context);

final firstIndent = "-${' ' * max(1, context.indentSize - 1)}";
final subsequentIndent = ' ' * firstIndent.length;

lines.add("$firstIndent${nodeYaml.first}");
for (int i = 1; i < nodeYaml.length; i++) {
lines.add("$subsequentIndent${nodeYaml[i]}");
}
}

return lines;
}
}

class MapNode extends Node {
final Map<String, Node> subnodesMap;

MapNode(this.subnodesMap);

@override
bool get requiresNewLine => subnodesMap.isNotEmpty;

@override
List<String> toYaml(YamlContext context) {
if (subnodesMap.isEmpty) {
return ['{}'];
}

final List<String> lines = [];

for (final entry in subnodesMap.entries) {
final key = entry.key;
final node = entry.value;

final nodeYaml = node.toYaml(context);

final indent = ' ' * context.indentSize;

if (node.requiresNewLine) {
lines.add("$key: ");
for (final line in nodeYaml) {
lines.add("$indent$line");
}
} else {
lines.add("$key: ${nodeYaml.first}");
for (int i = 1; i < nodeYaml.length; i++) {
lines.add("$indent${nodeYaml[i]}");
}
}
}

return lines;
}
}
10 changes: 10 additions & 0 deletions lib/src/yaml_context.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class YamlContext {
final int indentSize;

final bool allowUnquotedStrings;

YamlContext({
required this.indentSize,
required this.allowUnquotedStrings,
});
}
71 changes: 71 additions & 0 deletions lib/src/yaml_writer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'dart:convert';
import 'dart:math';

import 'package:yaml_writer/src/node.dart';

import 'yaml_context.dart';

@Deprecated('Use YamlWriter.')
typedef YAMLWriter = YamlWriter;

/// YAML Writer.
class YamlWriter extends Converter<Object?, String> {
static dynamic _defaultToEncodable(dynamic object) => object.toJson();

/// The indentation size.
///
/// Must be greater or equal to `1`.
///
/// Defaults to `2`.
final int indentSize;

/// If `true` it will allow unquoted strings.
final bool allowUnquotedStrings;

/// Used to convert objects to an encodable version.
final Object? Function(dynamic object) toEncodable;

YamlWriter({
int indentSize = 2,
this.allowUnquotedStrings = false,
Object? Function(dynamic object)? toEncodable,
}) : indentSize = max(1, indentSize),
toEncodable = toEncodable ?? _defaultToEncodable;

/// Converts [input] to an YAML document as [String].
///
/// This implements `dart:convert` [Converter].
///
/// - Calls [write].
@override
String convert(Object? input) => write(input);

/// Writes [object] to an YAML document as [String].
String write(Object? object) {
final node = _parseNode(object);
final context = YamlContext(
indentSize: indentSize,
allowUnquotedStrings: allowUnquotedStrings,
);
final yaml = node.toYaml(context);
return '${yaml.join('\n')}\n';
}

Node _parseNode(Object? object) {
if (object == null) {
return NullNode();
} else if (object is num) {
return NumNode(object);
} else if (object is bool) {
return BoolNode(object);
} else if (object is String) {
return StringNode(object);
} else if (object is List) {
return ListNode(object.map(_parseNode).toList());
} else if (object is Map) {
return MapNode(object.map((k, v) => MapEntry(k, _parseNode(v))));
} else {
return _parseNode(toEncodable(object));
}
}
}
Loading

0 comments on commit c713cdc

Please sign in to comment.