Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# See https://www.dartlang.org/guides/libraries/private-files

# Intellij & Android Studio files
.idea/

# Files and directories created by pub
.dart_tool/
.packages
Expand Down
6 changes: 3 additions & 3 deletions example/front_matter.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'dart:io';
import 'package:front_matter/front_matter.dart' as front_matter;
import 'package:front_matter/front_matter.dart';

// Example 1 - Parse a string.
void example1() async {
var file = new File('example/hello-world.md');
var fileContents = await file.readAsString();

var doc = front_matter.parse(fileContents);
var doc = FrontMatterDocument.fromText(fileContents);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this change really necessary? It introduces a breaking API change. Happy to discuss this in a separate issue (or PR) but I think this is the wrong PR for it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you feel this PR isn't the place for it, I can revert it back to the way it is done in your version of the package.

Copy link
Author

@mpfaff mpfaff Jun 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't mind me asking, as I'm working on reverting it now, is there a reason you have the parse method pointing directly to the parseContent method, except for the defaultDelimiter bit?

Would you mind if I just put the contents of parseContent directly into the parse method?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason you have the parse method pointing directly to the parseContent method?

Just a functional programming style.

Would you mind if I just put the contents of parseContent directly into the parse method?

I would, unfortunately. I've just pushed a new release 1.1.0 with some small changes. One of the changes is to rename the parseContent method to parser. I'd appreciate if you kept the current directory structure, with the two methods (parse and parseFile) exposed from front_matter.dart delegating out to the parser() from parser.dart.

I recommend rebasing with the latest commits from master and starting from there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I'm considering how to implement my changes in line with the things you've mentioned, is the FrontMatterDocument constructor supposed to be public? Is it supposed to be used outside of the package itself?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it supposed to be used outside of the package itself?

At the moment, no. But this is subject to change.

Copy link
Owner

@izolate izolate Jun 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaterialTime the more I think about it, the less I'm convinced that this FrontMatterDocument to String method is possible. The method to generate the YAML string from a Map is outside the scope of this package, and frankly better suited to the yaml package.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've looked around, and the yamlicious package might be a suitable alternative for generating the YAML portion of value. I hope that this can be used to resolve the issue of generating YAML strings being outside the scope of this package.


print("The author is ${doc.data['author']}");
print("The publish date is ${doc.data['date']}");
Expand All @@ -15,7 +15,7 @@ void example1() async {

// Example 2 - Read a file and parse its contents.
void example2() async {
var doc = await front_matter.parseFile('example/hello-world.md');
final doc = await FrontMatterDocument.fromFile('example/hello-world.md');

print("The author is ${doc.data['author']}");
print("The publish date is ${doc.data['date']}");
Expand Down
1 change: 0 additions & 1 deletion lib/front_matter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ library front_matter;

export 'src/front_matter_document.dart';
export 'src/front_matter_exception.dart';
export 'src/front_matter.dart' show parse, parseFile;
36 changes: 0 additions & 36 deletions lib/src/front_matter.dart

This file was deleted.

78 changes: 76 additions & 2 deletions lib/src/front_matter_document.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,85 @@
import 'dart:io';

import 'package:meta/meta.dart';
import 'package:yaml/yaml.dart';

import '../front_matter.dart';

// Default delimiter for YAML.
const defaultDelimiter = '---';

/// Document containing the original `value`, front matter `data` and `content`.
class FrontMatterDocument {
String value, content;
FrontMatterDocument({@required this.data, @required this.content});

FrontMatterDocument.fromText(String text,
{String delimiter = defaultDelimiter}) {
// Remove any leading whitespace.
final value = text.trimLeft();

// If there's no starting delimiter, there's no front matter.
if (!value.startsWith(delimiter)) {
content = value;
data = YamlMap();
return;
}

// Get the index of the closing delimiter.
final closeIndex = value.indexOf('\n' + delimiter);

// Get the raw front matter block between the opening and closing delimiters.
final frontMatter = value.substring(delimiter.length, closeIndex);

if (frontMatter.isNotEmpty) {
try {
// Parse the front matter as YAML.
data = loadYaml(frontMatter);
} catch (e) {
throw FrontMatterException(invalidYamlError);
}
}

// The content begins after the closing delimiter index.
content = value.substring(closeIndex + (delimiter.length + 1)).trim();

print('content: $content');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why return the content: prefix here? Just return content directly.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what's your reasoning for using print() here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about that. I believe that was there for testing and I forgot to remove it when I pushed the commit.

}

static fromFile(String path, {String delimiter = defaultDelimiter}) async {
var file = File(path);

// Throw an error if file not found.
if (!await file.exists()) {
throw FrontMatterException(fileNotFoundError);
}

try {
var text = await file.readAsString();
return FrontMatterDocument.fromText(text, delimiter: delimiter);
} catch (e) {
// Handle downstream errors, or throw one if file is not readable as text.
switch (e.message) {
case invalidYamlError:
rethrow;
default:
throw FrontMatterException(fileTypeError);
}
}
}

String content;

/// The parsed YAML front matter as a [YamlMap].
YamlMap data;

FrontMatterDocument(this.value);
String get value {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presuming this method was the reason for the PR, the value getter already exists in the latest version of this package. You can access the original raw value like so:

var str = "---\nfoo: bar\n---\nHello, world!";
var doc = fm.parse(str);

assert(doc.value, equals(str)); // true

Returning the original value directly is preferable over generating the raw string from the parsed document, to avoid any discrepancies.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for the getter is so that the document can be modified and converted back to a raw String to be stored. Eg. I'm planning to use it with Git storage.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just released 1.1.0 with a toString() method on FrontMatterDocument that returns the raw, original value. Is this what you need?

Perhaps we should pause this PR and open the discussion in the issue #1 ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Maybe I'm communicating it wrong, but the point of the getter is so that one can modify the data or content variables on a FrontMatterDocument can be modified and easily converted back to a raw front-matter document.

var newValue = '---\n';

for (final d in data.entries.toList()
..sort((a, b) => a.key.compareTo(b.key))) {
newValue += '${d.key}: ${d.value}\n';
}

return '$newValue---\n\n$content';
}
}
40 changes: 0 additions & 40 deletions lib/src/parse_content.dart

This file was deleted.

37 changes: 20 additions & 17 deletions test/front_matter_test.dart
Original file line number Diff line number Diff line change
@@ -1,48 +1,50 @@
import 'package:test/test.dart';
import 'package:front_matter/front_matter.dart' as fm;
import 'package:front_matter/src/front_matter.dart';
import 'package:front_matter/src/front_matter_document.dart';
import 'package:front_matter/src/front_matter_exception.dart';
import 'package:front_matter/front_matter.dart';

/// Represents a test with default parameters.
class Test {
String foo, baz, delimiter, content;
String foo, baz, bar, yawn, delimiter, content;

Test(
{this.foo = 'bar',
this.baz = 'qux',
this.bar = 'bing',
this.yawn = 'snore',
this.delimiter = defaultDelimiter,
this.content = '\nHello, world!'});
this.content = 'Hello, world!'});

String get frontMatter =>
'${this.delimiter}\nfoo: ${this.foo}\nbaz: ${this.baz}\n${this.delimiter}';
'${this.delimiter}\nbar: ${this.bar}\nbaz: ${this.baz}\nfoo: ${this.foo}\nyawn: ${this.yawn}\n${this.delimiter}';

/// Joins front matter to content.
String get value => this.frontMatter + this.content;
String get value => '${this.frontMatter}\n\n${this.content}';
}

void main() {
test('return type is `FrontMatterDocument`', () {
var text = 'foo';
var expected = FrontMatterDocument(text);
var result = fm.parse(text);
var expected = FrontMatterDocument.fromText(text);
var result = FrontMatterDocument.fromText(text);

expect(result.runtimeType, equals(expected.runtimeType));
});

test('parses front matter with default options', () {
var test = Test();
var result = fm.parse(test.value);

var result = FrontMatterDocument.fromText(test.value);

expect(result.value, equals(test.value));
expect(result.content, equals(test.content));
expect(result.data['foo'], equals(test.foo));
expect(result.data['baz'], equals(test.baz));
expect(result.data['bar'], equals(test.bar));
expect(result.data['yawn'], equals(test.yawn));
});

test('removes any leading spaces and linebreaks', () {
var test = Test();
var result = fm.parse(' \n\n\n${test.value}');
var result = FrontMatterDocument.fromText(' \n\n\n${test.value}');

expect(result.content, equals(test.content));
expect(result.data['foo'], equals(test.foo));
Expand All @@ -53,33 +55,34 @@ void main() {
var tests = delimiters.map((delimiter) => Test(delimiter: delimiter));

tests.forEach((test) {
var result = fm.parse(test.value, delimiter: test.delimiter);
var result =
FrontMatterDocument.fromText(test.value, delimiter: test.delimiter);
expect(result.content, equals(test.content));
expect(result.data['foo'], equals(test.foo));
});
});

test('throws FrontMatterException when YAML is invalid', () {
var input = '---\nINVALID\n---\nfoo';
expect(() => fm.parse(input),
expect(() => FrontMatterDocument.fromText(input),
throwsA(const TypeMatcher<FrontMatterException>()));
});

test('reads a file from disk and parses front matter', () async {
var result = await fm.parseFile('example/hello-world.md');
var result = await FrontMatterDocument.fromFile('example/hello-world.md');
expect(result.data['author'], equals('izolate'));
});

test('throws an error if file not found', () {
expect(
() async => await fm.parseFile('/path/to/nowhere'),
() async => await FrontMatterDocument.fromFile('/path/to/nowhere'),
throwsA(predicate((e) =>
e is FrontMatterException && e.message == fileNotFoundError)));
});

test('throws an error if file type is not supported', () {
expect(
() async => await fm.parseFile('test/dart.jpeg'),
() async => await FrontMatterDocument.fromFile('test/dart.jpeg'),
throwsA(predicate(
(e) => e is FrontMatterException && e.message == fileTypeError)));
});
Expand Down