Class: Brief::Document
Defined Under Namespace
Modules: Attachments, FrontMatter, Rendering, SourceMap, Templating
Classes: ContentExtractor, Section, Structure, Transformer
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
Methods included from SourceMap
#content_under_heading, #heading_details, #heading_element_tags, #heading_line_numbers, #line_numbers_for_heading, #next_sibling_heading_for
#attachment_paths, #has_attachments?, #render_attachments
Methods included from Templating
#generate_content
#data=, #frontmatter_line_count
Methods included from Rendering
#script_contents, #script_preamble, #to_html, #unwrapped_html
Constructor Details
#initialize(path, options = {}) ⇒ Document
Returns a new instance of Document.
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
# File 'lib/brief/document.rb', line 14
def initialize(path, options = {})
if path.respond_to?(:key?) && options.empty?
@frontmatter = path.to_mash
else
@path = Pathname(path)
end
@options = options.to_mash
if @path && self.path.exist?
@raw_content = self.path.read
load_frontmatter
elsif options[:contents]
@raw_content = options[:contents]
end
end
|
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(meth, *args, &block) ⇒ Object
326
327
328
329
330
331
332
|
# File 'lib/brief/document.rb', line 326
def method_missing(meth, *args, &block)
if data.respond_to?(meth)
data.send(meth, *args, &block)
else
super
end
end
|
Instance Attribute Details
#content ⇒ Object
Returns the value of attribute content.
12
13
14
|
# File 'lib/brief/document.rb', line 12
def content
@content
end
|
#frontmatter ⇒ Object
Returns the value of attribute frontmatter.
12
13
14
|
# File 'lib/brief/document.rb', line 12
def frontmatter
@frontmatter
end
|
#options ⇒ Object
Returns the value of attribute options.
12
13
14
|
# File 'lib/brief/document.rb', line 12
def options
@options
end
|
#path ⇒ Object
Returns the value of attribute path.
12
13
14
|
# File 'lib/brief/document.rb', line 12
def path
@path
end
|
#raw_content ⇒ Object
Returns the value of attribute raw_content.
12
13
14
|
# File 'lib/brief/document.rb', line 12
def raw_content
@raw_content
end
|
Class Method Details
.from_contents(content, frontmatter, &block) ⇒ Object
9
10
|
# File 'lib/brief/document.rb', line 9
def self.from_contents(content, frontmatter, &block)
end
|
Instance Method Details
#at(*args, &block) ⇒ Object
Returns a Nokogiri::HTML::Element
200
201
202
|
# File 'lib/brief/document.rb', line 200
def at(*args, &block)
parser.send(:at, *args, &block)
end
|
#attachments ⇒ Object
129
130
131
|
# File 'lib/brief/document.rb', line 129
def attachments
Array(data.attachments)
end
|
#briefcase ⇒ Object
143
144
145
|
# File 'lib/brief/document.rb', line 143
def briefcase
(@briefcase_root && Brief.cases[@briefcase_root.basename.to_s]) || Brief.case(true)
end
|
#clone ⇒ Object
35
36
37
|
# File 'lib/brief/document.rb', line 35
def clone
new(path, options)
end
|
#combined_data_and_content ⇒ Object
116
117
118
119
|
# File 'lib/brief/document.rb', line 116
def combined_data_and_content
return content if data.nil? || data.empty?
data.to_hash.to_yaml + "---\n\n#{ content }"
end
|
#content_hash ⇒ Object
55
56
57
|
# File 'lib/brief/document.rb', line 55
def content_hash
Digest::MD5.hexdigest(@content.to_s)
end
|
#content_stale? ⇒ Boolean
63
64
65
|
# File 'lib/brief/document.rb', line 63
def content_stale?
content_hash != file_hash
end
|
#css(*args, &block) ⇒ Object
Shortcut for querying the rendered HTML by css selectors.
This will allow for model data attributes to be pulled from the document contents.
Returns a Nokogiri::HTML::Element
195
196
197
|
# File 'lib/brief/document.rb', line 195
def css(*args, &block)
parser.send(:css, *args, &block)
end
|
#data ⇒ Object
121
122
123
|
# File 'lib/brief/document.rb', line 121
def data
frontmatter
end
|
#document ⇒ Object
31
32
33
|
# File 'lib/brief/document.rb', line 31
def document
self
end
|
#document_type ⇒ Object
259
260
261
|
# File 'lib/brief/document.rb', line 259
def document_type
options.fetch(:type) { document_type! }
end
|
#document_type! ⇒ Object
263
264
265
266
267
|
# File 'lib/brief/document.rb', line 263
def document_type!
existing = data && data.type
return existing if existing
parent_folder_name.try(:singularize)
end
|
#exist? ⇒ Boolean
240
241
242
|
# File 'lib/brief/document.rb', line 240
def exist?
path && path.exist?
end
|
#extension ⇒ Object
226
227
228
|
# File 'lib/brief/document.rb', line 226
def extension
path.extname
end
|
204
205
206
207
208
209
210
211
212
213
214
215
216
|
# File 'lib/brief/document.rb', line 204
def (*args)
options = args.
args = options.delete(:args) if options.is_a?(Hash) && options.key?(:args)
case
when options.empty? && args.length == 1 && args.first.is_a?(String)
results = css(args.first)
results = results.first if results.length > 1 && args.first.match(/:first-of-type/)
results.try(:text).to_s
else
binding.pry
end
end
|
#file_hash ⇒ Object
59
60
61
|
# File 'lib/brief/document.rb', line 59
def file_hash
Digest::MD5.hexdigest(path.read.to_s)
end
|
#fragment ⇒ Object
318
319
320
|
# File 'lib/brief/document.rb', line 318
def fragment
@fragment ||= Nokogiri::HTML.fragment(to_raw_html)
end
|
#has_sections? ⇒ Boolean
147
148
149
|
# File 'lib/brief/document.rb', line 147
def has_sections?
model_class.section_mappings.length > 0
end
|
#in_briefcase(briefcase) ⇒ Object
133
134
135
136
137
138
139
140
141
|
# File 'lib/brief/document.rb', line 133
def in_briefcase(briefcase)
@briefcase_root = briefcase.root
unless Brief::Util.ensure_child_path(briefcase.docs_path, path)
raise 'Invalid document path'
end
self
end
|
#include_attachments? ⇒ Boolean
125
126
127
|
# File 'lib/brief/document.rb', line 125
def include_attachments?
attachments.length > 0
end
|
#inspect ⇒ Object
47
48
49
|
# File 'lib/brief/document.rb', line 47
def inspect
"#{ model_class }.at_path(#{relative_path})"
end
|
#model_attributes ⇒ Object
230
231
232
233
234
|
# File 'lib/brief/document.rb', line 230
def model_attributes
(data || {}).to_hash
.merge(path: path, document: self)
.reverse_merge(type: document_type)
end
|
#model_class ⇒ Object
244
245
246
247
248
249
250
251
252
253
254
255
256
257
|
# File 'lib/brief/document.rb', line 244
def model_class
case
when @model_class
@model_class
when briefcase
briefcase.model_class_for(self)
when data && data.type
Brief::Model.for_type(data.type)
when parent_folder_name.length > 0
Brief::Model.for_folder_name(parent_folder_name)
else
raise 'Could not determine the model class to use for this document. Specify the type, or put it in a folder that maps to the correct type.'
end
end
|
#model_instance_registered? ⇒ Boolean
Each model class tracks the instances of the models created and ensures that there is a 1-1 relationship between a document path and the model.
276
277
278
279
280
|
# File 'lib/brief/document.rb', line 276
def model_instance_registered?
model_class && model_class.models.any? do |model|
model.path == path
end
end
|
#parent_folder_name ⇒ Object
269
270
271
|
# File 'lib/brief/document.rb', line 269
def parent_folder_name
path.parent.basename.to_s.downcase
end
|
#parser ⇒ Object
The Parser wraps the rendered HTML in a nokogiri element so we can easily manipulate it. Prior to doing so, we use the structure analyzer to build more metadata into the markup
297
298
299
300
301
302
303
304
305
|
# File 'lib/brief/document.rb', line 297
def parser
@parser ||= begin
structure.prescan
structure.create_wrappers.tap do |f|
transformer_for(f).all
end
end
end
|
#raw=(val) ⇒ Object
67
68
69
70
71
72
|
# File 'lib/brief/document.rb', line 67
def raw= val
@raw_set = true
@raw_content = val
@raw_content
end
|
#refresh! ⇒ Object
89
90
91
92
93
94
95
96
97
98
|
# File 'lib/brief/document.rb', line 89
def refresh!
@content = nil
@raw_content = path.read
@frontmatter = nil
@raw_frontmatter = nil
@refreshing = true
@content_hash = nil
load_frontmatter
true
end
|
#refreshed ⇒ Object
100
101
102
103
|
# File 'lib/brief/document.rb', line 100
def refreshed
refresh!
self
end
|
#relative_path ⇒ Object
51
52
53
|
# File 'lib/brief/document.rb', line 51
def relative_path
briefcase.present? ? path.relative_path_from(briefcase.docs_path) : path
end
|
#relative_path_identifier ⇒ Object
218
219
220
221
222
223
224
|
# File 'lib/brief/document.rb', line 218
def relative_path_identifier
if Brief.case
path.relative_path_from(Brief.case.root)
else
path.to_s
end
end
|
#respond_to?(*args) ⇒ Boolean
282
283
284
285
|
# File 'lib/brief/document.rb', line 282
def respond_to?(*args)
method = args.first
super || (data && data.respond_to?(method)) || (data && data.key?(method))
end
|
#save ⇒ Object
78
79
80
81
82
83
84
85
86
87
|
# File 'lib/brief/document.rb', line 78
def save
if set_raw?
file_contents = raw_content
else
file_contents = combined_data_and_content
end
path.open('w') {|fh| fh.write(file_contents) }
refresh!
end
|
#save! ⇒ Object
105
106
107
108
109
110
111
112
113
114
|
# File 'lib/brief/document.rb', line 105
def save!
if set_raw?
file_contents = raw_content
else
file_contents = combined_data_and_content
end
path.open('w+') {|fh| fh.write(file_contents) }
refresh!
end
|
#section_headings ⇒ Object
151
152
153
|
# File 'lib/brief/document.rb', line 151
def section_headings
sections.keys
end
|
#sections ⇒ Object
164
165
166
167
168
169
170
171
172
173
174
175
|
# File 'lib/brief/document.rb', line 164
def sections
mappings = model_class.section_mappings
@sections = {}.to_mash
mappings.each do |name, mapping|
fragment = css("section[data-heading='#{name}']").first
@sections[name.parameterize.downcase.underscore] = Brief::Document::Section.new(name, fragment, mapping)
end
@sections
end
|
#sections_data ⇒ Object
155
156
157
158
159
160
161
162
|
# File 'lib/brief/document.rb', line 155
def sections_data
section_headings.reduce({}) do |memo, heading|
section = sections.send(heading)
items = section.items rescue nil
memo[heading] = items if items
memo
end
end
|
#set_raw? ⇒ Boolean
74
75
76
|
# File 'lib/brief/document.rb', line 74
def set_raw?
!!@raw_set
end
|
#structure ⇒ Object
The structure analyzer of the document is responsible for grouping the content under the headings by wrapping them in divs, and creating relationships between the nodes. This is what lets us provide an easy iteration API on top of the parsed document
291
292
293
|
# File 'lib/brief/document.rb', line 291
def structure
@structure_analyzer ||= Brief::Document::Structure.new(fragment, raw_content.lines.to_a)
end
|
#title ⇒ Object
39
40
41
|
# File 'lib/brief/document.rb', line 39
def title
(data && data.title) || css('h1:first-of-type').text || path.to_s.split("/").last.gsub(/\..*/,'')
end
|
#to_model ⇒ Object
236
237
238
|
# File 'lib/brief/document.rb', line 236
def to_model
model_class.try(:new, model_attributes)
end
|
#to_s ⇒ Object
43
44
45
|
# File 'lib/brief/document.rb', line 43
def to_s
"#{ model_class }.at_path(#{relative_path})"
end
|
The transformer is responsible for performing content modifications on the rendered document. This is useful for supporting extensions that are driven by the markdown language.
TODO: This is hidden behind a feature flag, and requires the document to have metadata that specifies transform = true
313
314
315
316
|
# File 'lib/brief/document.rb', line 313
def transformer_for(doc_fragment=nil)
doc_fragment ||= fragment
@transformer ||= Brief::Document::Transformer.new(doc_fragment, self)
end
|
#type ⇒ Object
322
323
324
|
# File 'lib/brief/document.rb', line 322
def type
document_type
end
|