front_matter_parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ruby File.read('.ruby-version').strip
4
+
5
+ # Specify your gem's dependencies in front_matter_parser.gemspec
6
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 marc
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,123 @@
1
+ # FrontMatterParser
2
+
3
+ FrontMatterParser is a library to parse files or strings with YAML front matters. When working with files, it can automatically detect the syntax of a file from its extension and it supposes that the front matter is marked as that syntax comments.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'front_matter_parser'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install front_matter_parser
18
+
19
+ ## Usage
20
+
21
+ Front matters must be between two lines with three dashes `---`.
22
+
23
+ Given a file `example.md`:
24
+
25
+ ---
26
+ title: Hello World
27
+ category: Greetings
28
+ ---
29
+ Some actual content
30
+
31
+ You can parse it:
32
+
33
+ parsed = FrontMatterParser.parse_file('example.md')
34
+ parsed.front_matter #=> {'title' => 'Hello World'}
35
+ parsed.content #=> 'Some actual content'
36
+
37
+ You can apply directly `[]` method to get a front matter value:
38
+
39
+ parsed['category'] #=> 'Greetings'
40
+
41
+ ### Syntax autodetection
42
+
43
+ `FrontMatterParser` detects the syntax of a file by its extension and suppose that the front matter is within that syntax comments delimiters.
44
+
45
+ Given a file `example.haml`:
46
+
47
+ -#
48
+ ---
49
+ title: Hello
50
+ ---
51
+ Content
52
+
53
+ The `-#` and the indentation enclose the front matter as a comment. `FrontMatterParser` is aware of it and you can simply do:
54
+
55
+ title = FrontMatterParser.parse_file('example.haml')['title'] #=> 'Hello'
56
+
57
+ Following there is a relation of known syntaxes and their known comment delimiters:
58
+
59
+ <pre>
60
+ | Syntax | Single line comment | Start multiline comment | End multiline comment |
61
+ | ------ | ------------------- | ----------------------- | ---------------------- |
62
+ | haml | | -# | (indentation) |
63
+ | slim | | / | (indentation) |
64
+ | liquid | | <% comment %> | <% endcomment %> |
65
+ | md | | | |
66
+ | html | | &lt;!-- | --&gt; |
67
+ | coffee | # | | |
68
+ | sass | // | | |
69
+ | scss | // | | |
70
+ </pre>
71
+
72
+ You can provide your own by passing `autodetect: false` and options for single line comment delimiter (`:comment`) or start multiline comment (`:start_comment`) and end multiline comment (`:end_comment`) delimiters. If `:start_comment` is provided but it isn't `:end_comment`, then it is supposed that the multiline comment is ended by indentation.
73
+
74
+ FrontMatterParser.parse_file('example.haml', autodetect: false, start_comment: '<!--', end_comment: '-->') # start and end multiline comment delimiters
75
+ FrontMatterParser.parse_file('example.slim', autodetect: false, start_comment: '/!') # multiline comment closed by indentation
76
+ FrontMatterParser.parse_file('example.foo', autodetect: false, comment: '#') # single line comments
77
+
78
+ ### Parsing a string
79
+
80
+ You can as well parse a string, providing manually its comment delimiters if needed:
81
+
82
+ string = File.read('example.slim')
83
+ FrontMatterParser.parse(string, start_comment: '/')
84
+
85
+ ## Contributing
86
+
87
+ 1. Fork it
88
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
89
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
90
+ 4. Push to the branch (`git push origin my-new-feature`)
91
+ 5. Create new Pull Request
92
+
93
+ ## Release Policy
94
+
95
+ `front_matter_parser` follows the principles of [semantic versioning](http://semver.org/).
96
+
97
+ ## To Do
98
+
99
+ * Add more known syntaxes.
100
+ * Allow configuration of global front matter delimiters. It would be easy, but I'm not sure if too useful.
101
+ * Allow different formats (as JSON). Again, I'm not sure if it would be very useful.
102
+
103
+ ## Other ruby front matter parsers
104
+
105
+ * [front-matter](https://github.com/zhaocai/front-matter.rb) Can parse YAML front matters with single line comments delimiters. YAML must be correctly indented.
106
+ * [ruby_front_matter](https://github.com/F-3r/ruby_front_matter) Can parse JSON front matters and can configure front matter global delimiters, but does not accept comment delimiters.
107
+
108
+ ## LICENSE
109
+
110
+ Copyright 2013 Marc Busqué - <marc@lamarciana.com>
111
+
112
+ This program is free software: you can redistribute it and/or modify
113
+ it under the terms of the GNU General Public License as published by
114
+ the Free Software Foundation, either version 3 of the License, or
115
+ (at your option) any later version.
116
+
117
+ This program is distributed in the hope that it will be useful,
118
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
119
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
120
+ GNU General Public License for more details.
121
+
122
+ You should have received a copy of the GNU General Public License
123
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'autospec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rspec-core', 'autospec')
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rake' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rake', 'rake')
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rspec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rspec-core', 'rspec')
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'front_matter_parser/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "front_matter_parser"
8
+ spec.version = FrontMatterParser::VERSION
9
+ spec.authors = ["marc"]
10
+ spec.email = ["marc@lamarciana.com"]
11
+ spec.description = %q{Library to parse files or strings with YAML front matters with syntax autodetection.}
12
+ spec.summary = %q{FrontMatterParser is a library to parse files or strings with YAML front matters. When working with files, it can automatically detect the syntax of a file from its extension and it supposes that the front matter is marked as that syntax comments.}
13
+ spec.homepage = "https://github.com/laMarciana/front_matter_parser"
14
+ spec.license = "LGPL3"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5", "<1.6"
22
+ spec.add_development_dependency "rake", "~>10.1"
23
+ spec.add_development_dependency "rspec", "~> 2.14"
24
+ end
@@ -0,0 +1,103 @@
1
+ require 'yaml'
2
+ require "front_matter_parser/version"
3
+ require "front_matter_parser/parsed"
4
+
5
+ # FrontMatterParser module is the entry point to parse strings or files with YAML front matters. When working with files, it can automatically detect the syntax of a file from its extension and it supposes that the front matter is marked as that syntax comments.
6
+ module FrontMatterParser
7
+ # {Hash {Symbol => Array}} Comments delimiters used in FrontMatterParser known syntaxes. Keys are file extensions, and values are three elements array:
8
+ #
9
+ # * First element is single line comment delimiter.
10
+ # * Second element is the start multiline comment delimiter.
11
+ # * Third element is the end multiline comment delimiter. If it is `nil` and start multiline comment delimiter isn't, it means that the comment is closed by indentation.
12
+ COMMENT_DELIMITERS = {
13
+ slim: [nil, '/', nil],
14
+ html: [nil, '<!--', '-->'],
15
+ coffee: ['#', nil, nil],
16
+ haml: [nil, '-#', nil],
17
+ liquid: [nil, '<% comment %>', '<% endcomment %>'],
18
+ sass: ['//', nil, nil],
19
+ scss: ['//', nil, nil],
20
+ md: [nil, nil, nil],
21
+ }
22
+
23
+ # Parses a string into a {Parsed} instance. For comment options, see {COMMENT_DELIMITERS} values (but they are not limited to those for the known syntaxes).
24
+ #
25
+ # @param string [String] The string to parse
26
+ # @param opts [Hash] Options
27
+ # @option opts [String, nil] :comment Single line comment delimiter
28
+ # @option opts [String, nil] :start_comment Start multiline comment delimiter
29
+ # @option opts [String, nil] :end_comment End multiline comment delimiter
30
+ # @return [Parsed]
31
+ # @raise [ArgumentError] If end_comment option is provided but not start_comment
32
+ # @see COMMENT_DELIMITERS
33
+ def self.parse(string, opts = {})
34
+ opts = {
35
+ comment: nil,
36
+ start_comment: nil,
37
+ end_comment: nil,
38
+ }.merge(opts)
39
+ raise(ArgumentError, "If you provide the `end_comment` option, you must provide also the `start_comment` option") if (opts[:end_comment] != nil and opts[:start_comment] == nil)
40
+ parsed = Parsed.new
41
+ if matches = (string.match(/
42
+ # Start of string
43
+ \A
44
+ # Zero or more space characters
45
+ ([[:space:]]*)
46
+ # Start multiline comment
47
+ #{'(?-x:(?<multiline_comment_indentation>^[[:blank:]]*)'+opts[:start_comment]+'[[:blank:]]*[\n\r][[:space:]]*)' unless opts[:start_comment].nil?}
48
+ # Begin front matter
49
+ (?-x:^[[:blank:]]*#{opts[:comment]}[[:blank:]]*---[[:blank:]]*$[\n\r])
50
+ # The front matter
51
+ (?<front_matter>.*)
52
+ # End front matter
53
+ (?-x:^[[:blank:]]*#{opts[:comment]}[[:blank:]]*---[[:blank:]]*$[\n\r])
54
+ # End multiline comment
55
+ #{'(?-x:\k<multiline_comment_indentation>)' if opts[:end_comment].nil? and not opts[:start_comment].nil?}
56
+ #{'(?-x:[[:space:]]*^[[:blank:]]*'+opts[:end_comment]+'[[:blank:]]*[\n\r])' if not opts[:end_comment].nil?}
57
+ # The content
58
+ (?<content>.*)
59
+ # End of string
60
+ \z
61
+ /mx))
62
+ front_matter = matches[:front_matter].gsub(/^#{opts[:comment]}/, '')
63
+ parsed.front_matter = YAML.load(front_matter)
64
+ parsed.content = matches[:content]
65
+ else
66
+ parsed.front_matter = {}
67
+ parsed.content = string
68
+ end
69
+ parsed
70
+ end
71
+
72
+ # Parses a file into a {Parsed} instance. If autodetect option is `true`, comment delimiters are guessed from the file extension. If it is `false` comment options are taken into consideration. See {COMMENT_DELIMITERS} for a list of known syntaxes and the comment delimiters values.
73
+ #
74
+ # @param pathname [String] The path to the file
75
+ # @param opts [Hash] Options
76
+ # @option opts [Boolean] :autodetect If it is true, FrontMatterParser uses the comment delimiters known for the syntax of the file and comment options are ignored. If it is false, comment options are taken into consideration.
77
+ # @option opts [String, nil] :comment Single line comment delimiter
78
+ # @option opts [String, nil] :start_comment Start multiline comment delimiter
79
+ # @option opts [String, nil] :end_comment End multiline comment delimiter
80
+ # @return [Parsed]
81
+ # @raise [ArgumentError] If autodetect option is false, and start_comment option is provided but not end_comment
82
+ # @raise [RuntimeError] If the syntax of the file (the extension) is not within the keys of {COMMENT_DELIMITERS}
83
+ # @see COMMENT_DELIMITERS
84
+ def self.parse_file(pathname, opts={})
85
+ opts = {
86
+ autodetect: true,
87
+ comment: nil,
88
+ start_comment: nil,
89
+ end_comment: nil,
90
+ }.merge(opts)
91
+ if opts[:autodetect]
92
+ ext = File.extname(pathname)[1 .. -1].to_sym
93
+ raise(RuntimeError, "Comment delimiters for extension #{ext.to_s} not known. Please, call #parse_file without autodetect option and provide comment delimiters.") unless COMMENT_DELIMITERS.has_key?(ext)
94
+ comment_delimiters = COMMENT_DELIMITERS[ext]
95
+ opts[:comment] = comment_delimiters[0]
96
+ opts[:start_comment] = comment_delimiters[1]
97
+ opts[:end_comment] = comment_delimiters[2]
98
+ end
99
+ File.open(pathname) do |file|
100
+ parse(file.read, comment: opts[:comment], start_comment: opts[:start_comment], end_comment: opts[:end_comment])
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,18 @@
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
8
+
9
+ alias_method :to_hash, :front_matter
10
+
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]
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module FrontMatterParser
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # ---
2
+ # title: Hello
3
+ # ---
4
+ Content
File without changes
@@ -0,0 +1,5 @@
1
+ -#
2
+ ---
3
+ title: Hello
4
+ ---
5
+ Content
@@ -0,0 +1,6 @@
1
+ <!--
2
+ ---
3
+ title: Hello
4
+ ---
5
+ -->
6
+ Content
@@ -0,0 +1,6 @@
1
+ { %comment% }
2
+ ---
3
+ title: Hello
4
+ ---
5
+ { %endcomment% }
6
+ Content
@@ -0,0 +1,4 @@
1
+ ---
2
+ title: Hello
3
+ ---
4
+ Content
@@ -0,0 +1,4 @@
1
+ // ---
2
+ // title: Hello
3
+ // ---
4
+ Content
@@ -0,0 +1,4 @@
1
+ // ---
2
+ // title: Hello
3
+ // ---
4
+ Content
@@ -0,0 +1,5 @@
1
+ /
2
+ ---
3
+ title: Hello
4
+ ---
5
+ Content
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ module FrontMatterParser
4
+ describe Parsed do
5
+
6
+ let(:sample) { {'title' => 'hello'} }
7
+
8
+ let(:string) { %Q(
9
+ ---
10
+ title: hello
11
+ ---
12
+ Content) }
13
+
14
+ let(:parsed) { FrontMatterParser.parse(string) }
15
+
16
+ describe "#to_hash" do
17
+ it "returns @front_matter" do
18
+ expect(parsed.to_hash).to eq(sample)
19
+ end
20
+ end
21
+
22
+ describe "#[]" do
23
+ it "returns the front matter value for the given key" do
24
+ expect(parsed['title']).to eq('hello')
25
+ end
26
+ end
27
+ end
28
+ end