front_matter_parser 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +19 -0
- data/.gitignore +1 -0
- data/.overcommit.yml +54 -0
- data/.overcommit_gems.rb +15 -0
- data/.rspec +1 -0
- data/.rubocop.yml +8 -0
- data/.travis.yml +18 -1
- data/Dockerfile +8 -0
- data/Gemfile +0 -2
- data/README.md +64 -24
- data/Rakefile +5 -3
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +7 -0
- data/front_matter_parser.gemspec +9 -5
- data/lib/front_matter_parser.rb +10 -114
- data/lib/front_matter_parser/loader.rb +11 -0
- data/lib/front_matter_parser/loader/yaml.rb +18 -0
- data/lib/front_matter_parser/parsed.rb +25 -14
- data/lib/front_matter_parser/parser.rb +80 -0
- data/lib/front_matter_parser/syntax_parser.rb +28 -0
- data/lib/front_matter_parser/syntax_parser/factorizable.rb +30 -0
- data/lib/front_matter_parser/syntax_parser/indentation_comment.rb +48 -0
- data/lib/front_matter_parser/syntax_parser/multi_line_comment.rb +49 -0
- data/lib/front_matter_parser/syntax_parser/single_line_comment.rb +63 -0
- data/lib/front_matter_parser/version.rb +3 -1
- data/spec/fixtures/example +6 -0
- data/spec/front_matter_parser/loader/yaml_spec.rb +15 -0
- data/spec/front_matter_parser/parsed_spec.rb +11 -21
- data/spec/front_matter_parser/parser_spec.rb +111 -0
- data/spec/front_matter_parser/syntax_parser/indentation_comment_spec.rb +146 -0
- data/spec/front_matter_parser/syntax_parser/multi_line_comment_spec.rb +249 -0
- data/spec/front_matter_parser/syntax_parser/single_line_comment_spec.rb +156 -0
- data/spec/front_matter_parser_spec.rb +3 -296
- data/spec/spec_helper.rb +7 -1
- data/spec/support/matcher.rb +8 -0
- metadata +86 -46
- data/spec/fixtures/example.coffee +0 -4
- data/spec/fixtures/example.erb +0 -6
- data/spec/fixtures/example.foo +0 -0
- data/spec/fixtures/example.haml +0 -5
- data/spec/fixtures/example.liquid +0 -6
- data/spec/fixtures/example.md +0 -4
- data/spec/fixtures/example.sass +0 -4
- data/spec/fixtures/example.scss +0 -4
- data/spec/fixtures/example.slim +0 -5
- data/spec/support/strings.rb +0 -41
- data/spec/support/syntaxs.rb +0 -14
- data/spec/support/utils.rb +0 -6
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'front_matter_parser/loader/yaml'
|
4
|
+
|
5
|
+
module FrontMatterParser
|
6
|
+
# This module includes front matter loaders (from a string -usually extracted
|
7
|
+
# with a {SyntaxParser}- to hash). They must respond to a `::call` method
|
8
|
+
# which accepts the String as argument and respond with a Hash.
|
9
|
+
module Loader
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module FrontMatterParser
|
6
|
+
module Loader
|
7
|
+
# {Loader} that uses YAML library
|
8
|
+
class Yaml
|
9
|
+
# Loads a hash front matter from a string
|
10
|
+
#
|
11
|
+
# @param string [String] front matter string representation
|
12
|
+
# @return [Hash] front matter hash representation
|
13
|
+
def self.call(string)
|
14
|
+
YAML.safe_load(string)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,18 +1,29 @@
|
|
1
|
-
#
|
2
|
-
class FrontMatterParser::Parsed
|
3
|
-
# @!attribute [rw] front_matter
|
4
|
-
# @return [Hash{String => String, Array, Hash}] the parsed front matter
|
5
|
-
# @!attribute [rw] content
|
6
|
-
# @return [String] the parsed content
|
7
|
-
attr_accessor :front_matter, :content
|
1
|
+
# frozen_string_literal: true
|
8
2
|
|
9
|
-
|
3
|
+
module FrontMatterParser
|
4
|
+
# Result of parsing front matter and content from a string
|
5
|
+
class Parsed
|
6
|
+
# @!attribute [rw] front_matter
|
7
|
+
# @see #initialize
|
8
|
+
attr_reader :front_matter
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@
|
10
|
+
# @!attribute [rw] content
|
11
|
+
# @see #initialize
|
12
|
+
attr_reader :content
|
13
|
+
|
14
|
+
# @param front_matter [Hash] parsed front_matter
|
15
|
+
# @param content [String] parsed content
|
16
|
+
def initialize(front_matter:, content:)
|
17
|
+
@front_matter = front_matter
|
18
|
+
@content = content
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns front matter value for given key
|
22
|
+
#
|
23
|
+
# @param key [String] key for desired value
|
24
|
+
# @return [String, Array, # Hash] desired value
|
25
|
+
def [](key)
|
26
|
+
front_matter[key]
|
27
|
+
end
|
17
28
|
end
|
18
29
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FrontMatterParser
|
4
|
+
# Entry point to parse a front matter from a string or file.
|
5
|
+
class Parser
|
6
|
+
# @!attribute [r] syntax_parser
|
7
|
+
# Current syntax parser in use. See {SyntaxParser}
|
8
|
+
attr_reader :syntax_parser
|
9
|
+
|
10
|
+
# @!attribute [r] loader
|
11
|
+
# Current loader in use. See {Loader} for details
|
12
|
+
attr_reader :loader
|
13
|
+
|
14
|
+
# Parses front matter and content from given pathname, inferring syntax from
|
15
|
+
# the extension or, otherwise, using syntax_parser argument.
|
16
|
+
#
|
17
|
+
# @param pathname [String]
|
18
|
+
# @param syntax_parser [Object] see {SyntaxParser}
|
19
|
+
# @param loader [Object] see {Loader}
|
20
|
+
# @return [Parsed] parsed front matter and content
|
21
|
+
def self.parse_file(pathname, syntax_parser: nil, loader: Loader::Yaml)
|
22
|
+
syntax_parser ||= syntax_from_pathname(pathname)
|
23
|
+
File.open(pathname) do |file|
|
24
|
+
new(syntax_parser, loader: loader).call(file.read)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @!visibility private
|
29
|
+
def self.syntax_from_pathname(pathname)
|
30
|
+
File.extname(pathname)[1..-1].to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
# @!visibility private
|
34
|
+
def self.syntax_parser_from_symbol(syntax)
|
35
|
+
Kernel.const_get(
|
36
|
+
"FrontMatterParser::SyntaxParser::#{syntax.capitalize}"
|
37
|
+
).new
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param syntax_parser [Object] Syntax parser to use. It can be one of two
|
41
|
+
# things:
|
42
|
+
#
|
43
|
+
# - An actual object which acts like a parser. See {SyntaxParser} for
|
44
|
+
# details.
|
45
|
+
#
|
46
|
+
# - A symbol, in which case it refers to a parser
|
47
|
+
# `FrontMatterParser::SyntaxParser::#{symbol.capitalize}` which can be
|
48
|
+
# initialized without arguments
|
49
|
+
#
|
50
|
+
# @param loader [Object] Front matter loader to use. See {Loader} for
|
51
|
+
# details.
|
52
|
+
def initialize(syntax_parser, loader: Loader::Yaml)
|
53
|
+
@syntax_parser = infer_syntax_parser(syntax_parser)
|
54
|
+
@loader = loader
|
55
|
+
end
|
56
|
+
|
57
|
+
# Parses front matter and content from given string
|
58
|
+
#
|
59
|
+
# @param string [String]
|
60
|
+
# @return [Parsed] parsed front matter and content
|
61
|
+
# :reek:FeatureEnvy
|
62
|
+
def call(string)
|
63
|
+
match = syntax_parser.call(string)
|
64
|
+
front_matter, content =
|
65
|
+
if match
|
66
|
+
[loader.call(match[:front_matter]), match[:content]]
|
67
|
+
else
|
68
|
+
[{}, string]
|
69
|
+
end
|
70
|
+
Parsed.new(front_matter: front_matter, content: content)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def infer_syntax_parser(syntax_parser)
|
76
|
+
return syntax_parser unless syntax_parser.is_a?(Symbol)
|
77
|
+
self.class.syntax_parser_from_symbol(syntax_parser)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'front_matter_parser/syntax_parser/factorizable'
|
4
|
+
require 'front_matter_parser/syntax_parser/multi_line_comment'
|
5
|
+
require 'front_matter_parser/syntax_parser/indentation_comment'
|
6
|
+
require 'front_matter_parser/syntax_parser/single_line_comment'
|
7
|
+
|
8
|
+
module FrontMatterParser
|
9
|
+
# This module includes parsers for different syntaxes. They respond to
|
10
|
+
# a method `#call`, which takes a string as argument and responds with
|
11
|
+
# a hash interface with `:front_matter` and `:content` keys, or `nil` if no
|
12
|
+
# front matter is found.
|
13
|
+
#
|
14
|
+
# :reek:TooManyConstants
|
15
|
+
module SyntaxParser
|
16
|
+
Coffee = SingleLineComment['#']
|
17
|
+
Sass = SingleLineComment['//']
|
18
|
+
Scss = SingleLineComment['//']
|
19
|
+
|
20
|
+
Html = MultiLineComment['<!--', '-->']
|
21
|
+
Erb = MultiLineComment['<%#', '%>']
|
22
|
+
Liquid = MultiLineComment['{% comment %}', '{% endcomment %}']
|
23
|
+
Md = MultiLineComment['', '']
|
24
|
+
|
25
|
+
Slim = IndentationComment['/']
|
26
|
+
Haml = IndentationComment['-#']
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FrontMatterParser
|
4
|
+
module SyntaxParser
|
5
|
+
# This is just a helper to allow creating syntax parsers with a more terse
|
6
|
+
# syntax, without the need of explicitly creating descendant classes for the
|
7
|
+
# most general cases. See {SyntaxParser} for examples in use.
|
8
|
+
module Factorizable
|
9
|
+
# @param delimiters [String] Splat arguments with all comment delimiters
|
10
|
+
# used by the parser
|
11
|
+
#
|
12
|
+
# @return [Object] A base class of self with a `delimiters` class method
|
13
|
+
# added which returns an array with given comment delimiters
|
14
|
+
def [](*delimiters)
|
15
|
+
delimiters = delimiters.map { |delimiter| Regexp.escape(delimiter) }
|
16
|
+
create_base_class(delimiters)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def create_base_class(delimiters)
|
22
|
+
parser = Class.new(self)
|
23
|
+
parser.define_singleton_method(:delimiters) do
|
24
|
+
delimiters
|
25
|
+
end
|
26
|
+
parser
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FrontMatterParser
|
4
|
+
module SyntaxParser
|
5
|
+
# Parser for syntaxes which use comments ended by indentation
|
6
|
+
class IndentationComment
|
7
|
+
extend Factorizable
|
8
|
+
|
9
|
+
# @!attribute [r] regexp
|
10
|
+
# A regexp that returns two groups: front_matter and content
|
11
|
+
attr_reader :regexp
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@regexp = build_regexp(*self.class.delimiters)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @see SyntaxParser
|
18
|
+
def call(string)
|
19
|
+
string.match(regexp)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @see Factorizable
|
23
|
+
# :nocov:
|
24
|
+
def self.delimiters
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# rubocop:disable Metrics/MethodLength
|
31
|
+
def build_regexp(delimiter)
|
32
|
+
/
|
33
|
+
\A
|
34
|
+
[[:space:]]*
|
35
|
+
(?<multiline_comment_indentation>^[[:blank:]]*)
|
36
|
+
#{delimiter}
|
37
|
+
[[:space:]]*
|
38
|
+
---
|
39
|
+
(?<front_matter>.*)
|
40
|
+
---
|
41
|
+
[[:blank:]]*$[\n\r]
|
42
|
+
(?<content>.*)
|
43
|
+
\z
|
44
|
+
/mx
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FrontMatterParser
|
4
|
+
module SyntaxParser
|
5
|
+
# Parser for syntaxes which use end and finish comment delimiters
|
6
|
+
class MultiLineComment
|
7
|
+
extend Factorizable
|
8
|
+
|
9
|
+
# @!attribute [r] regexp
|
10
|
+
# A regexp that returns two groups: front_matter and content
|
11
|
+
attr_reader :regexp
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@regexp = build_regexp(*self.class.delimiters)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @see SyntaxParser
|
18
|
+
def call(string)
|
19
|
+
string.match(regexp)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @see Factorizable
|
23
|
+
# :nocov:
|
24
|
+
def self.delimiters
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# rubocop:disable Metrics/MethodLength
|
31
|
+
def build_regexp(start_delimiter, end_delimiter)
|
32
|
+
/
|
33
|
+
\A
|
34
|
+
[[:space:]]*
|
35
|
+
#{start_delimiter}
|
36
|
+
[[:space:]]*
|
37
|
+
---
|
38
|
+
(?<front_matter>.*)
|
39
|
+
---
|
40
|
+
[[:space:]]*
|
41
|
+
#{end_delimiter}
|
42
|
+
[[:blank:]]*$[\n\r]
|
43
|
+
(?<content>.*)
|
44
|
+
\z
|
45
|
+
/mx
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FrontMatterParser
|
4
|
+
module SyntaxParser
|
5
|
+
# Parser for syntaxes which each comment is for a single line
|
6
|
+
class SingleLineComment
|
7
|
+
extend Factorizable
|
8
|
+
|
9
|
+
# @!attribute [r] regexp
|
10
|
+
# A regexp that returns two groups: front_matter (with comment delimiter
|
11
|
+
# in it) and content
|
12
|
+
attr_reader :regexp
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@regexp = build_regexp(*self.class.delimiters)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @see SyntaxParser
|
19
|
+
def call(string)
|
20
|
+
match = string.match(regexp)
|
21
|
+
if match
|
22
|
+
front_matter = self.class.remove_delimiter(match[:front_matter])
|
23
|
+
{
|
24
|
+
front_matter: front_matter,
|
25
|
+
content: match[:content]
|
26
|
+
}
|
27
|
+
else
|
28
|
+
match
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @see Factorizable
|
33
|
+
# :nocov:
|
34
|
+
def self.delimiters
|
35
|
+
raise NotImplementedError
|
36
|
+
end
|
37
|
+
|
38
|
+
# @!visibility private
|
39
|
+
def self.remove_delimiter(front_matter)
|
40
|
+
delimiter = delimiters.first
|
41
|
+
front_matter.gsub(/^[\s\t]*#{delimiter}/, '')
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# rubocop:disable Metrics/MethodLength
|
47
|
+
def build_regexp(delimiter)
|
48
|
+
/
|
49
|
+
\A
|
50
|
+
[[:space:]]*
|
51
|
+
#{delimiter}[[:blank:]]*
|
52
|
+
---
|
53
|
+
(?<front_matter>.*)
|
54
|
+
^[[:blank:]]*#{delimiter}[[:blank:]]*
|
55
|
+
---
|
56
|
+
[[:blank:]]*$[\n\r]
|
57
|
+
(?<content>.*)
|
58
|
+
\z
|
59
|
+
/mx
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/spec/fixtures/example
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FrontMatterParser::Loader::Yaml do
|
6
|
+
describe '::call' do
|
7
|
+
it 'loads using yaml parser' do
|
8
|
+
string = "{title: 'hello'}"
|
9
|
+
|
10
|
+
expect(described_class.call(string)).to eq(
|
11
|
+
'title' => 'hello'
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,28 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module FrontMatterParser
|
4
|
-
describe Parsed do
|
5
|
-
|
6
|
-
let(:sample) { {'title' => 'hello'} }
|
1
|
+
# frozen_string_literal: true
|
7
2
|
|
8
|
-
|
9
|
-
---
|
10
|
-
title: hello
|
11
|
-
---
|
12
|
-
Content) }
|
3
|
+
require 'spec_helper'
|
13
4
|
|
14
|
-
|
5
|
+
describe FrontMatterParser::Parsed do
|
6
|
+
subject(:parsed) do
|
7
|
+
described_class.new(front_matter: front_matter, content: content)
|
8
|
+
end
|
15
9
|
|
16
|
-
|
17
|
-
|
18
|
-
expect(parsed.to_hash).to eq(sample)
|
19
|
-
end
|
20
|
-
end
|
10
|
+
let(:front_matter) { { 'title' => 'hello' } }
|
11
|
+
let(:content) { 'Content' }
|
21
12
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
13
|
+
describe '#[]' do
|
14
|
+
it 'returns front_matter value for given key' do
|
15
|
+
expect(parsed['title']).to eq('hello')
|
26
16
|
end
|
27
17
|
end
|
28
18
|
end
|