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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +19 -0
  3. data/.gitignore +1 -0
  4. data/.overcommit.yml +54 -0
  5. data/.overcommit_gems.rb +15 -0
  6. data/.rspec +1 -0
  7. data/.rubocop.yml +8 -0
  8. data/.travis.yml +18 -1
  9. data/Dockerfile +8 -0
  10. data/Gemfile +0 -2
  11. data/README.md +64 -24
  12. data/Rakefile +5 -3
  13. data/bin/console +15 -0
  14. data/bin/setup +8 -0
  15. data/docker-compose.yml +7 -0
  16. data/front_matter_parser.gemspec +9 -5
  17. data/lib/front_matter_parser.rb +10 -114
  18. data/lib/front_matter_parser/loader.rb +11 -0
  19. data/lib/front_matter_parser/loader/yaml.rb +18 -0
  20. data/lib/front_matter_parser/parsed.rb +25 -14
  21. data/lib/front_matter_parser/parser.rb +80 -0
  22. data/lib/front_matter_parser/syntax_parser.rb +28 -0
  23. data/lib/front_matter_parser/syntax_parser/factorizable.rb +30 -0
  24. data/lib/front_matter_parser/syntax_parser/indentation_comment.rb +48 -0
  25. data/lib/front_matter_parser/syntax_parser/multi_line_comment.rb +49 -0
  26. data/lib/front_matter_parser/syntax_parser/single_line_comment.rb +63 -0
  27. data/lib/front_matter_parser/version.rb +3 -1
  28. data/spec/fixtures/example +6 -0
  29. data/spec/front_matter_parser/loader/yaml_spec.rb +15 -0
  30. data/spec/front_matter_parser/parsed_spec.rb +11 -21
  31. data/spec/front_matter_parser/parser_spec.rb +111 -0
  32. data/spec/front_matter_parser/syntax_parser/indentation_comment_spec.rb +146 -0
  33. data/spec/front_matter_parser/syntax_parser/multi_line_comment_spec.rb +249 -0
  34. data/spec/front_matter_parser/syntax_parser/single_line_comment_spec.rb +156 -0
  35. data/spec/front_matter_parser_spec.rb +3 -296
  36. data/spec/spec_helper.rb +7 -1
  37. data/spec/support/matcher.rb +8 -0
  38. metadata +86 -46
  39. data/spec/fixtures/example.coffee +0 -4
  40. data/spec/fixtures/example.erb +0 -6
  41. data/spec/fixtures/example.foo +0 -0
  42. data/spec/fixtures/example.haml +0 -5
  43. data/spec/fixtures/example.liquid +0 -6
  44. data/spec/fixtures/example.md +0 -4
  45. data/spec/fixtures/example.sass +0 -4
  46. data/spec/fixtures/example.scss +0 -4
  47. data/spec/fixtures/example.slim +0 -5
  48. data/spec/support/strings.rb +0 -41
  49. data/spec/support/syntaxs.rb +0 -14
  50. 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
- # Parsed is the result of calling {FrontMatterParser.parse} or {FrontMatterParser.parse_file} in {FrontMatterParser}. It keeps the front matter and the content parsed and it has some useful methods to work with them.
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
- alias_method :to_hash, :front_matter
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
- # Returns the front matter value for the given key
12
- #
13
- # @param key [String] The key of the front matter
14
- # @return [String, Array, Hash] The value of the front matter for the given key
15
- def [](key)
16
- @front_matter[key]
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FrontMatterParser
2
- VERSION = "0.0.4"
4
+ VERSION = '0.1.0'
3
5
  end
@@ -0,0 +1,6 @@
1
+ <!--
2
+ ---
3
+ title: hello
4
+ ---
5
+ -->
6
+ Content
@@ -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
- require 'spec_helper'
2
-
3
- module FrontMatterParser
4
- describe Parsed do
5
-
6
- let(:sample) { {'title' => 'hello'} }
1
+ # frozen_string_literal: true
7
2
 
8
- let(:string) { %Q(
9
- ---
10
- title: hello
11
- ---
12
- Content) }
3
+ require 'spec_helper'
13
4
 
14
- let(:parsed) { FrontMatterParser.parse(string) }
5
+ describe FrontMatterParser::Parsed do
6
+ subject(:parsed) do
7
+ described_class.new(front_matter: front_matter, content: content)
8
+ end
15
9
 
16
- describe "#to_hash" do
17
- it "returns @front_matter" do
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
- describe "#[]" do
23
- it "returns the front matter value for the given key" do
24
- expect(parsed['title']).to eq('hello')
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