cff 0.2.0 → 0.9.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.
data/Rakefile CHANGED
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2018 Robert Haines.
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
2
4
  #
3
5
  # Licensed under the Apache License, Version 2.0 (the "License");
4
6
  # you may not use this file except in compliance with the License.
@@ -12,22 +14,25 @@
12
14
  # See the License for the specific language governing permissions and
13
15
  # limitations under the License.
14
16
 
15
- require "bundler/gem_tasks"
16
- require "rake/testtask"
17
- require "rdoc/task"
17
+ require 'bundler/gem_tasks'
18
+ require 'rake/testtask'
19
+ require 'rdoc/task'
20
+ require 'rubocop/rake_task'
18
21
 
19
- task :default => :test
22
+ task default: :test
20
23
 
21
24
  Rake::TestTask.new(:test) do |t|
22
- t.libs << "test"
23
- t.libs << "lib"
24
- t.test_files = FileList["test/**/*_test.rb"]
25
+ t.libs << 'test'
26
+ t.libs << 'lib'
27
+ t.test_files = FileList['test/**/*_test.rb']
25
28
  end
26
29
 
27
30
  RDoc::Task.new do |r|
28
- r.main = "README.md"
29
- r.rdoc_files.include("README.md", "LICENCE", "lib/**/*.rb")
30
- r.options << "--markup=markdown"
31
- r.options << "--tab-width=2"
31
+ r.main = 'README.md'
32
+ r.rdoc_files.include('README.md', 'LICENCE', 'CHANGES.md', 'lib/**/*.rb')
33
+ r.options << '--markup=markdown'
34
+ r.options << '--tab-width=2'
32
35
  r.options << "-t Ruby CFF Library version #{::CFF::VERSION}"
33
36
  end
37
+
38
+ RuboCop::RakeTask.new
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "cff"
4
+ require 'bundler/setup'
5
+ require 'cff'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "cff"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start(__FILE__)
data/cff.gemspec CHANGED
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2018 Robert Haines.
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
2
4
  #
3
5
  # Licensed under the Apache License, Version 2.0 (the "License");
4
6
  # you may not use this file except in compliance with the License.
@@ -12,34 +14,51 @@
12
14
  # See the License for the specific language governing permissions and
13
15
  # limitations under the License.
14
16
 
15
- lib = File.expand_path("../lib", __FILE__)
16
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
17
- require "cff/version"
17
+ require_relative 'lib/cff/version'
18
18
 
19
19
  Gem::Specification.new do |spec|
20
- spec.name = "cff"
20
+ spec.name = 'cff'
21
21
  spec.version = CFF::VERSION
22
- spec.authors = ["Robert Haines"]
23
- spec.email = ["robert.haines@manchester.ac.uk"]
22
+ spec.authors = [
23
+ 'Robert Haines',
24
+ 'The Ruby Citation File Format Developers'
25
+ ]
26
+ spec.email = ['robert.haines@manchester.ac.uk']
27
+
28
+ spec.summary = 'A Ruby library for manipulating CITATION.cff files.'
29
+ spec.description = 'See https://citation-file-format.github.io/ ' \
30
+ 'for more info.'
31
+ spec.homepage = 'https://github.com/citation-file-format/ruby-cff'
32
+ spec.license = 'Apache-2.0'
24
33
 
25
- spec.summary = "A Ruby library for manipulating CITATION.cff files."
26
- spec.description = "See https://citation-file-format.github.io/ for more info."
27
- spec.homepage = "https://github.com/hainesr/ruby-cff"
28
- spec.license = "Apache-2.0"
34
+ spec.metadata = {
35
+ 'bug_tracker_uri' => 'https://github.com/citation-file-format/ruby-cff/issues',
36
+ 'changelog_uri' => 'https://github.com/citation-file-format/ruby-cff/blob/main/CHANGES.md',
37
+ 'documentation_uri' => 'https://citation-file-format.github.io/ruby-cff/',
38
+ 'source_code_uri' => 'https://github.com/citation-file-format/ruby-cff'
39
+ }
29
40
 
30
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
31
- f.match(%r{^(test|spec|features)/})
41
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
42
+ f.match(%r{^((test|spec|features)/|\.)})
32
43
  end
33
- spec.bindir = "exe"
44
+
45
+ spec.bindir = 'exe'
34
46
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
- spec.require_paths = ["lib"]
47
+ spec.require_paths = ['lib']
48
+
49
+ spec.required_ruby_version = '>= 2.6'
36
50
 
37
- spec.required_ruby_version = ">= 2.2.0"
51
+ spec.add_runtime_dependency 'json_schema', '~> 0.20.0'
52
+ spec.add_runtime_dependency 'language_list', '~> 1.2'
38
53
 
39
- spec.add_development_dependency "bundler", "~> 1.16"
40
- spec.add_development_dependency "rake", "~> 10.0"
41
- spec.add_development_dependency "minitest", "~> 5.0"
42
- spec.add_development_dependency "coveralls", "~> 0.8"
43
- spec.add_development_dependency "test_construct", "~> 2.0"
44
- spec.add_development_dependency "rdoc", "~> 6.0"
54
+ spec.add_development_dependency 'minitest', '~> 5.14'
55
+ spec.add_development_dependency 'rake', '~> 13.0'
56
+ spec.add_development_dependency 'rdoc', '~> 6.3'
57
+ spec.add_development_dependency 'rubocop', '~> 1.15'
58
+ spec.add_development_dependency 'rubocop-minitest', '~> 0.13'
59
+ spec.add_development_dependency 'rubocop-performance', '~> 1.11.0'
60
+ spec.add_development_dependency 'rubocop-rake', '~> 0.5.0'
61
+ spec.add_development_dependency 'simplecov', '~> 0.20.0'
62
+ spec.add_development_dependency 'simplecov-lcov', '~> 0.8.0'
63
+ spec.add_development_dependency 'test_construct', '~> 2.0'
45
64
  end
data/lib/cff.rb CHANGED
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2018 Robert Haines.
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
2
4
  #
3
5
  # Licensed under the Apache License, Version 2.0 (the "License");
4
6
  # you may not use this file except in compliance with the License.
@@ -12,16 +14,11 @@
12
14
  # See the License for the specific language governing permissions and
13
15
  # limitations under the License.
14
16
 
15
- require "date"
16
- require "yaml"
17
+ require 'date'
18
+ require 'json'
19
+ require 'yaml'
17
20
 
18
- require "cff/version"
19
- require "cff/util"
20
- require "cff/model-part"
21
- require "cff/person"
22
- require "cff/entity"
23
- require "cff/model"
24
- require "cff/file"
21
+ require 'language_list'
25
22
 
26
23
  # This library provides a Ruby interface to manipulate CITATION.cff files. The
27
24
  # primary entry points are Model and File.
@@ -29,5 +26,22 @@ require "cff/file"
29
26
  # See the [CITATION.cff documentation](https://citation-file-format.github.io/)
30
27
  # for more details.
31
28
  module CFF
32
-
29
+ SCHEMA_PATH = ::File.join(__dir__, 'schema', '1.2.0.json') # :nodoc:
30
+ SCHEMA_FILE = JSON.parse(::File.read(SCHEMA_PATH)) # :nodoc:
33
31
  end
32
+
33
+ require 'cff/version'
34
+ require 'cff/errors'
35
+ require 'cff/util'
36
+ require 'cff/licensable'
37
+ require 'cff/validatable'
38
+ require 'cff/model_part'
39
+ require 'cff/person'
40
+ require 'cff/entity'
41
+ require 'cff/identifier'
42
+ require 'cff/reference'
43
+ require 'cff/model'
44
+ require 'cff/file'
45
+ require 'cff/formatter/formatter'
46
+ require 'cff/formatter/apa_formatter'
47
+ require 'cff/formatter/bibtex_formatter'
data/lib/cff/entity.rb CHANGED
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2018 Robert Haines.
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
2
4
  #
3
5
  # Licensed under the Apache License, Version 2.0 (the "License");
4
6
  # you may not use this file except in compliance with the License.
@@ -12,42 +14,55 @@
12
14
  # See the License for the specific language governing permissions and
13
15
  # limitations under the License.
14
16
 
15
- #
17
+ ##
16
18
  module CFF
17
19
 
18
20
  # An Entity can represent different types of entities, e.g., a publishing
19
21
  # company, or conference. Like a Person, an Entity might have a number of
20
22
  # roles, such as author, contact, editor, etc.
23
+ #
24
+ # Entity implements all of the fields listed in the
25
+ # [CFF standard](https://citation-file-format.github.io/). All fields
26
+ # are simple strings and can be set as such. A field which has not been set
27
+ # will return the empty string. The simple fields are (with defaults in
28
+ # parentheses):
29
+ #
30
+ # * `address`
31
+ # * `city`
32
+ # * `country`
33
+ # * `email`
34
+ # * `date_end` - *Note:* returns a `Date` object
35
+ # * `date_start` - *Note:* returns a `Date` object
36
+ # * `fax`
37
+ # * `location`
38
+ # * `name`
39
+ # * `orcid`
40
+ # * `post_code`
41
+ # * `region`
42
+ # * `tel`
43
+ # * `website`
21
44
  class Entity < ModelPart
22
45
 
23
46
  ALLOWED_FIELDS = [
24
- 'address',
25
- 'city',
26
- 'country',
27
- 'email',
28
- 'date-end',
29
- 'date-start',
30
- 'fax',
31
- 'location',
32
- 'name',
33
- 'orcid',
34
- 'post-code',
35
- 'region',
36
- 'tel',
37
- 'website'
47
+ 'address', 'city', 'country', 'email', 'date-end', 'date-start', 'fax',
48
+ 'location', 'name', 'orcid', 'post-code', 'region', 'tel', 'website'
38
49
  ].freeze # :nodoc:
39
50
 
40
51
  # :call-seq:
41
52
  # new(name) -> Entity
53
+ # new(name) { |entity| block } -> Entity
42
54
  #
43
55
  # Create a new Entity with the supplied name.
44
56
  def initialize(param)
45
- if Hash === param
46
- super(param)
57
+ if param.is_a?(Hash)
58
+ @fields = param
59
+ @fields.default = ''
47
60
  else
48
61
  @fields = Hash.new('')
49
62
  @fields['name'] = param
50
63
  end
64
+
65
+ yield self if block_given?
51
66
  end
52
67
 
53
68
  # :call-seq:
@@ -56,9 +71,7 @@ module CFF
56
71
  # Set the `date-end` field. If a non-Date object is passed in it will
57
72
  # be parsed into a Date.
58
73
  def date_end=(date)
59
- unless Date === date
60
- date = Date.parse(date)
61
- end
74
+ date = Date.parse(date) unless date.is_a?(Date)
62
75
 
63
76
  @fields['date-end'] = date
64
77
  end
@@ -69,12 +82,9 @@ module CFF
69
82
  # Set the `date-start` field. If a non-Date object is passed in it will
70
83
  # be parsed into a Date.
71
84
  def date_start=(date)
72
- unless Date === date
73
- date = Date.parse(date)
74
- end
85
+ date = Date.parse(date) unless date.is_a?(Date)
75
86
 
76
87
  @fields['date-start'] = date
77
88
  end
78
-
79
89
  end
80
90
  end
data/lib/cff/errors.rb ADDED
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ ##
18
+ module CFF
19
+
20
+ # Error is the base class for all errors raised by this library.
21
+ class Error < RuntimeError
22
+
23
+ def initialize(message = nil) # :nodoc:
24
+ super
25
+ end
26
+ end
27
+
28
+ # ValidationError is raised when a CFF file fails validation. It contains
29
+ # details of each failure that was detected by the underlying JsonSchema
30
+ # library, which is used to perform the validation.
31
+ #
32
+ # Additionally, the `invalid_filename` flag is used to indicate whether the
33
+ # CFF file is named correctly. This is only used when validating a File;
34
+ # validating a Model directly will not set this flag to `true`.
35
+ class ValidationError < Error
36
+
37
+ # The list of JsonSchema::ValidationErrors found by the validator.
38
+ attr_reader :errors
39
+
40
+ # If a File was validated, was its filename invalid?
41
+ attr_reader :invalid_filename
42
+
43
+ def initialize(errors, invalid_filename: false) # :nodoc:
44
+ super('Validation error')
45
+ @errors = errors
46
+ @invalid_filename = invalid_filename
47
+ end
48
+
49
+ def to_s # :nodoc:
50
+ "#{super}: (Invalid filename: #{@invalid_filename}) #{@errors.join(' ')}"
51
+ end
52
+ end
53
+ end
data/lib/cff/file.rb CHANGED
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2018 Robert Haines.
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
2
4
  #
3
5
  # Licensed under the Apache License, Version 2.0 (the "License");
4
6
  # you may not use this file except in compliance with the License.
@@ -12,56 +14,245 @@
12
14
  # See the License for the specific language governing permissions and
13
15
  # limitations under the License.
14
16
 
15
- #
17
+ ##
16
18
  module CFF
17
19
 
18
20
  # File provides direct access to a CFF Model, with the addition of some
19
21
  # filesystem utilities.
22
+ #
23
+ # To be a fully compliant and valid CFF file its filename should be
24
+ # 'CITATION.cff'. This class allows you to create files with any filename,
25
+ # and to validate the contents of those files independently of the preferred
26
+ # filename.
20
27
  class File
21
28
 
29
+ # A comment to be inserted at the top of the resultant CFF file.
30
+ attr_reader :comment
31
+
32
+ # The filename of this CFF file.
33
+ attr_reader :filename
34
+
22
35
  YAML_HEADER = "---\n" # :nodoc:
36
+ CFF_COMMENT = [
37
+ "This CITATION.cff file was created by ruby-cff (v #{CFF::VERSION}).",
38
+ 'Gem: https://rubygems.org/gems/cff',
39
+ 'CFF: https://citation-file-format.github.io/'
40
+ ].freeze # :nodoc:
41
+ CFF_VALID_FILENAME = 'CITATION.cff' # :nodoc:
23
42
 
24
43
  # :call-seq:
25
- # new(title) -> File
26
- # new(model) -> File
44
+ # new(filename, title) -> File
45
+ # new(filename, model) -> File
27
46
  #
28
47
  # Create a new File. Either a pre-existing Model can be passed in or, as
29
48
  # with Model itself, a title can be supplied to initalize a new File.
30
49
  #
31
50
  # All methods provided by Model are also available directly on File
32
51
  # objects.
33
- def initialize(param)
34
- unless Model === param
35
- param = Model.new(param)
36
- end
52
+ def initialize(filename, param, comment = CFF_COMMENT, create: false)
53
+ param = Model.new(param) unless param.is_a?(Model)
37
54
 
55
+ @filename = filename
38
56
  @model = param
57
+ @comment = comment
58
+ @dirty = create
39
59
  end
40
60
 
41
61
  # :call-seq:
42
- # read(file) -> File
62
+ # read(filename) -> File
43
63
  #
44
64
  # Read a file and parse it for subsequent manipulation.
45
65
  def self.read(file)
46
- new(YAML::load_file(file))
66
+ content = ::File.read(file)
67
+ comment = File.parse_comment(content)
68
+
69
+ new(
70
+ file, YAML.safe_load(content, permitted_classes: [Date, Time]), comment
71
+ )
47
72
  end
48
73
 
49
74
  # :call-seq:
50
- # write(file, model)
51
- # write(file, yaml)
75
+ # open(filename) -> File
76
+ # open(filename) { |cff| block }
52
77
  #
53
- # Write the supplied model or yaml string to `file`.
54
- def self.write(file, cff)
55
- unless String === cff
56
- cff = cff.to_yaml
78
+ # With no associated block, File.open is a synonym for ::read. If the
79
+ # optional code block is given, it will be passed the opened file as an
80
+ # argument and the File object will automatically be written (if edited)
81
+ # and closed when the block terminates.
82
+ #
83
+ # File.open will create a new file if one does not already exist with the
84
+ # provided file name.
85
+ def self.open(file)
86
+ if ::File.exist?(file)
87
+ content = ::File.read(file)
88
+ comment = File.parse_comment(content)
89
+ yaml = YAML.safe_load(content, permitted_classes: [Date, Time])
90
+ else
91
+ comment = CFF_COMMENT
92
+ yaml = ''
93
+ end
94
+
95
+ cff = new(file, yaml, comment, create: (yaml == ''))
96
+ return cff unless block_given?
97
+
98
+ begin
99
+ yield cff
100
+ ensure
101
+ cff.write
57
102
  end
103
+ end
58
104
 
59
- ::File.write(file, cff[YAML_HEADER.length...-1])
105
+ # :call-seq:
106
+ # validate(filename, fail_on_filename: true) -> Array
107
+ #
108
+ # Read a file and return an array with the result. The result array is a
109
+ # three-element array, with `true`/`false` at index 0 to indicate
110
+ # pass/fail, an array of schema validation errors at index 1 (if any), and
111
+ # `true`/`false` at index 2 to indicate whether the filename passed/failed
112
+ # validation.
113
+ #
114
+ # You can choose whether filename validation failure should cause overall
115
+ # validation failure with the `fail_on_filename` parameter (default: true).
116
+ def self.validate(file, fail_on_filename: true)
117
+ File.read(file).validate(fail_on_filename: fail_on_filename)
118
+ end
119
+
120
+ # :call-seq:
121
+ # validate!(filename, fail_on_filename: true)
122
+ #
123
+ # Read a file and raise a ValidationError upon failure. If an error is
124
+ # raised it will contain the detected validation failures for further
125
+ # inspection.
126
+ #
127
+ # You can choose whether filename validation failure should cause overall
128
+ # validation failure with the `fail_on_filename` parameter (default: true).
129
+ def self.validate!(file, fail_on_filename: true)
130
+ File.read(file).validate!(fail_on_filename: fail_on_filename)
131
+ end
132
+
133
+ # :call-seq:
134
+ # write(filename, File)
135
+ # write(filename, Model)
136
+ # write(filename, yaml)
137
+ #
138
+ # Write the supplied File, Model or yaml string to `file`.
139
+ def self.write(file, cff, comment = '')
140
+ comment = cff.comment if cff.respond_to?(:comment)
141
+ cff = cff.to_yaml unless cff.is_a?(String)
142
+ content = File.format_comment(comment) + cff[YAML_HEADER.length...-1]
143
+
144
+ ::File.write(file, content)
145
+ end
146
+
147
+ # :call-seq:
148
+ # validate(fail_fast: false, fail_on_filename: true) -> Array
149
+ #
150
+ # Validate this file and return an array with the result. The result array
151
+ # is a three-element array, with `true`/`false` at index 0 to indicate
152
+ # pass/fail, an array of schema validation errors at index 1 (if any), and
153
+ # `true`/`false` at index 2 to indicate whether the filename passed/failed
154
+ # validation.
155
+ #
156
+ # You can choose whether filename validation failure should cause overall
157
+ # validation failure with the `fail_on_filename` parameter (default: true).
158
+ def validate(fail_fast: false, fail_on_filename: true)
159
+ valid_filename = (::File.basename(@filename) == CFF_VALID_FILENAME)
160
+ result = (@model.validate(fail_fast: fail_fast) << valid_filename)
161
+ result[0] &&= valid_filename if fail_on_filename
162
+
163
+ result
164
+ end
165
+
166
+ # :call-seq:
167
+ # validate!(fail_fast: false, fail_on_filename: true)
168
+ #
169
+ # Validate this file and raise a ValidationError upon failure. If an error
170
+ # is raised it will contain the detected validation failures for further
171
+ # inspection.
172
+ #
173
+ # You can choose whether filename validation failure should cause overall
174
+ # validation failure with the `fail_on_filename` parameter (default: true).
175
+ def validate!(fail_fast: false, fail_on_filename: true)
176
+ result = validate(
177
+ fail_fast: fail_fast, fail_on_filename: fail_on_filename
178
+ )
179
+ return if result[0]
180
+
181
+ raise ValidationError.new(result[1], invalid_filename: !result[2])
182
+ end
183
+
184
+ # :call-seq:
185
+ # write(save_as: filename)
186
+ #
187
+ # Write this CFF File. The `save_as` parameter can be used to save a new
188
+ # copy of this CFF File under a different filename, leaving the original
189
+ # file untouched. If `save_as` is used then the internal filename of the
190
+ # File will be updated to the supplied filename.
191
+ def write(save_as: nil)
192
+ unless save_as.nil?
193
+ @filename = save_as
194
+ @dirty = true
195
+ end
196
+
197
+ File.write(@filename, @model, @comment) if @dirty
198
+ @dirty = false
199
+ end
200
+
201
+ # :call-seq:
202
+ # comment = string or array
203
+ #
204
+ # A comment to be inserted at the top of the resultant CFF file. This can
205
+ # be supplied as a simple string or an array of strings. When the file is
206
+ # saved this comment is formatted as follows:
207
+ #
208
+ # * a simple string is split into 75 character lines and `'# '` is prepended
209
+ # to each line;
210
+ # * an array of strings is joined into a single string with `'\n'` and
211
+ # `'# '` is prepended to each line;
212
+ #
213
+ # If you care about formatting, use an array of strings for your comment,
214
+ # if not, use a single string.
215
+ def comment=(comment)
216
+ @dirty = true
217
+ @comment = comment
218
+ end
219
+
220
+ def to_yaml # :nodoc:
221
+ @model.to_yaml
60
222
  end
61
223
 
62
224
  def method_missing(name, *args) # :nodoc:
63
- @model.send name, *args
225
+ if @model.respond_to?(name)
226
+ @dirty = true if name.to_s.end_with?('=') # Remove to_s when Ruby >2.6.
227
+ @model.send(name, *args)
228
+ else
229
+ super
230
+ end
231
+ end
232
+
233
+ def respond_to_missing?(name, *all) # :nodoc:
234
+ @model.respond_to?(name, *all)
64
235
  end
65
236
 
237
+ def self.format_comment(comment) # :nodoc:
238
+ return '' if comment.empty?
239
+
240
+ comment = comment.scan(/.{1,75}/) if comment.is_a?(String)
241
+ c = comment.map do |l|
242
+ l.empty? ? '#' : "# #{l}"
243
+ end.join("\n")
244
+
245
+ "#{c}\n\n"
246
+ end
247
+
248
+ def self.parse_comment(content) # :nodoc:
249
+ content = content.split("\n")
250
+
251
+ content.reduce([]) do |acc, line|
252
+ break acc unless line.start_with?('#')
253
+
254
+ acc << line.sub(/^#+/, '').strip
255
+ end
256
+ end
66
257
  end
67
258
  end