diff --git a/.gitignore b/.gitignore index 7bf00e8..d7688d4 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/example/front_matter.dart b/example/front_matter.dart index a3c0f10..2b5698b 100644 --- a/example/front_matter.dart +++ b/example/front_matter.dart @@ -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); print("The author is ${doc.data['author']}"); print("The publish date is ${doc.data['date']}"); @@ -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']}"); diff --git a/lib/front_matter.dart b/lib/front_matter.dart index 42ff052..b105155 100644 --- a/lib/front_matter.dart +++ b/lib/front_matter.dart @@ -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; diff --git a/lib/src/front_matter.dart b/lib/src/front_matter.dart deleted file mode 100644 index 279e35c..0000000 --- a/lib/src/front_matter.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'front_matter_document.dart'; -import 'front_matter_exception.dart'; -import 'parse_content.dart'; - -// Default delimiter for YAML. -const defaultDelimiter = '---'; - -/// Parses a [text] string to extract the front matter. -FrontMatterDocument parse(String text, {String delimiter = defaultDelimiter}) => - parseContent(text, delimiter: delimiter); - -/// Reads a file at [path] and parses the content to extract the front matter. -Future parseFile(String path, - {String delimiter = defaultDelimiter}) async { - var file = new File(path); - - // Throw an error if file not found. - if (!await file.exists()) { - throw FrontMatterException(fileNotFoundError); - } - - try { - var text = await file.readAsString(); - return parseContent(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); - } - } -} diff --git a/lib/src/front_matter_document.dart b/lib/src/front_matter_document.dart index dfa26ae..9c1ffb7 100644 --- a/lib/src/front_matter_document.dart +++ b/lib/src/front_matter_document.dart @@ -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'); + } + + 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 { + 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'; + } } diff --git a/lib/src/parse_content.dart b/lib/src/parse_content.dart deleted file mode 100644 index 55b5511..0000000 --- a/lib/src/parse_content.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:yaml/yaml.dart'; -import 'front_matter_document.dart'; -import 'front_matter_exception.dart'; - -/// Extracts and parses YAML front matter from a [String]. -/// -/// Returns a [FrontMatterDocument] comprising the parsed YAML front matter -/// `data` [YamlMap], and the remaining `content` [String]. -/// -/// Throws a [FrontMatterException] if front matter contains invalid YAML. -FrontMatterDocument parseContent(String text, {String delimiter}) { - var doc = FrontMatterDocument(text); - - // Remove any leading whitespace. - var value = text.trimLeft(); - - // If there's no starting delimiter, there's no front matter. - if (!value.startsWith(delimiter)) { - return doc; - } - - // 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. - var frontMatter = value.substring(delimiter.length, closeIndex); - - if (frontMatter.isNotEmpty) { - try { - // Parse the front matter as YAML. - doc.data = loadYaml(frontMatter); - // The content begins after the closing delimiter index. - doc.content = value.substring(closeIndex + (delimiter.length + 1)); - } catch (e) { - throw FrontMatterException(invalidYamlError); - } - } - - return doc; -} diff --git a/test/front_matter_test.dart b/test/front_matter_test.dart index 1bd8cf6..0a66869 100644 --- a/test/front_matter_test.dart +++ b/test/front_matter_test.dart @@ -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)); @@ -53,7 +55,8 @@ 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)); }); @@ -61,25 +64,25 @@ void main() { test('throws FrontMatterException when YAML is invalid', () { var input = '---\nINVALID\n---\nfoo'; - expect(() => fm.parse(input), + expect(() => FrontMatterDocument.fromText(input), throwsA(const TypeMatcher())); }); 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))); });