Class: Brief::Document
Defined Under Namespace
Modules: Attachments, FrontMatter, Rendering, Templating
Classes: ContentExtractor, Section, Structure, Transformer
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
#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.
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
# File 'lib/brief/document.rb', line 13
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
316
317
318
319
320
321
322
|
# File 'lib/brief/document.rb', line 316
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.
11
12
13
|
# File 'lib/brief/document.rb', line 11
def content
@content
end
|
#frontmatter ⇒ Object
Returns the value of attribute frontmatter.
11
12
13
|
# File 'lib/brief/document.rb', line 11
def frontmatter
@frontmatter
end
|
#options ⇒ Object
Returns the value of attribute options.
11
12
13
|
# File 'lib/brief/document.rb', line 11
def options
@options
end
|
#path ⇒ Object
Returns the value of attribute path.
11
12
13
|
# File 'lib/brief/document.rb', line 11
def path
@path
end
|
#raw_content ⇒ Object
Returns the value of attribute raw_content.
11
12
13
|
# File 'lib/brief/document.rb', line 11
def raw_content
@raw_content
end
|
Class Method Details
.from_contents(content, frontmatter, &block) ⇒ Object
8
9
|
# File 'lib/brief/document.rb', line 8
def self.from_contents(content, frontmatter, &block)
end
|
Instance Method Details
#at(*args, &block) ⇒ Object
Returns a Nokogiri::HTML::Element
190
191
192
|
# File 'lib/brief/document.rb', line 190
def at(*args, &block)
parser.send(:at, *args, &block)
end
|
#attachments ⇒ Object
119
120
121
|
# File 'lib/brief/document.rb', line 119
def attachments
Array(data.attachments)
end
|
#briefcase ⇒ Object
133
134
135
|
# File 'lib/brief/document.rb', line 133
def briefcase
(@briefcase_root && Brief.cases[@briefcase_root.basename.to_s]) || Brief.case(true)
end
|
#combined_data_and_content ⇒ Object
106
107
108
109
|
# File 'lib/brief/document.rb', line 106
def combined_data_and_content
return content if data.nil? || data.empty?
frontmatter.to_hash.to_yaml + "---\n\n#{ content }"
end
|
#content_hash ⇒ Object
50
51
52
|
# File 'lib/brief/document.rb', line 50
def content_hash
Digest::MD5.hexdigest(@content.to_s)
end
|
#content_stale? ⇒ Boolean
58
59
60
|
# File 'lib/brief/document.rb', line 58
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
185
186
187
|
# File 'lib/brief/document.rb', line 185
def css(*args, &block)
parser.send(:css, *args, &block)
end
|
#data ⇒ Object
111
112
113
|
# File 'lib/brief/document.rb', line 111
def data
frontmatter
end
|
#document ⇒ Object
30
31
32
|
# File 'lib/brief/document.rb', line 30
def document
self
end
|
#document_type ⇒ Object
249
250
251
|
# File 'lib/brief/document.rb', line 249
def document_type
options.fetch(:type) { document_type! }
end
|
#document_type! ⇒ Object
253
254
255
256
257
|
# File 'lib/brief/document.rb', line 253
def document_type!
existing = data && data.type
return existing if existing
parent_folder_name.try(:singularize)
end
|
#exist? ⇒ Boolean
230
231
232
|
# File 'lib/brief/document.rb', line 230
def exist?
path && path.exist?
end
|
#extension ⇒ Object
216
217
218
|
# File 'lib/brief/document.rb', line 216
def extension
path.extname
end
|
194
195
196
197
198
199
200
201
202
203
204
205
206
|
# File 'lib/brief/document.rb', line 194
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
54
55
56
|
# File 'lib/brief/document.rb', line 54
def file_hash
Digest::MD5.hexdigest(path.read.to_s)
end
|
#fragment ⇒ Object
308
309
310
|
# File 'lib/brief/document.rb', line 308
def fragment
@fragment ||= Nokogiri::HTML.fragment(to_raw_html)
end
|
#has_sections? ⇒ Boolean
137
138
139
|
# File 'lib/brief/document.rb', line 137
def has_sections?
model_class.section_mappings.length > 0
end
|
#in_briefcase(briefcase) ⇒ Object
123
124
125
126
127
128
129
130
131
|
# File 'lib/brief/document.rb', line 123
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
115
116
117
|
# File 'lib/brief/document.rb', line 115
def include_attachments?
attachments.length > 0
end
|
#inspect ⇒ Object
42
43
44
|
# File 'lib/brief/document.rb', line 42
def inspect
"#{ model_class }.at_path(#{relative_path})"
end
|
#model_attributes ⇒ Object
220
221
222
223
224
|
# File 'lib/brief/document.rb', line 220
def model_attributes
(data || {}).to_hash
.merge(path: path, document: self)
.reverse_merge(type: document_type)
end
|
#model_class ⇒ Object
234
235
236
237
238
239
240
241
242
243
244
245
246
247
|
# File 'lib/brief/document.rb', line 234
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.
266
267
268
269
270
|
# File 'lib/brief/document.rb', line 266
def model_instance_registered?
model_class && model_class.models.any? do |model|
model.path == path
end
end
|
#parent_folder_name ⇒ Object
259
260
261
|
# File 'lib/brief/document.rb', line 259
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
287
288
289
290
291
292
293
294
295
|
# File 'lib/brief/document.rb', line 287
def parser
@parser ||= begin
structure.prescan
structure.create_wrappers.tap do |f|
transformer_for(f).all if data.transform
end
end
end
|
#raw=(val) ⇒ Object
62
63
64
65
66
67
|
# File 'lib/brief/document.rb', line 62
def raw= val
@raw_set = true
@raw_content = val
@raw_content
end
|
#refresh! ⇒ Object
84
85
86
87
88
89
90
91
92
93
|
# File 'lib/brief/document.rb', line 84
def refresh!
@content = nil
@raw_content = path.read
@frontmatter = nil
@raw_frontmatter = nil
@refreshing = true
@content_hash = nil
load_frontmatter
true
end
|
#relative_path ⇒ Object
46
47
48
|
# File 'lib/brief/document.rb', line 46
def relative_path
briefcase.present? ? path.relative_path_from(briefcase.docs_path) : path
end
|
#relative_path_identifier ⇒ Object
208
209
210
211
212
213
214
|
# File 'lib/brief/document.rb', line 208
def relative_path_identifier
if Brief.case
path.relative_path_from(Brief.case.root)
else
path.to_s
end
end
|
#respond_to?(*args) ⇒ Boolean
272
273
274
275
|
# File 'lib/brief/document.rb', line 272
def respond_to?(*args)
method = args.first
super || (data && data.respond_to?(method)) || (data && data.key?(method))
end
|
#save ⇒ Object
73
74
75
76
77
78
79
80
81
82
|
# File 'lib/brief/document.rb', line 73
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
95
96
97
98
99
100
101
102
103
104
|
# File 'lib/brief/document.rb', line 95
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
141
142
143
|
# File 'lib/brief/document.rb', line 141
def section_headings
sections.keys
end
|
#sections ⇒ Object
154
155
156
157
158
159
160
161
162
163
164
165
|
# File 'lib/brief/document.rb', line 154
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
145
146
147
148
149
150
151
152
|
# File 'lib/brief/document.rb', line 145
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
69
70
71
|
# File 'lib/brief/document.rb', line 69
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
281
282
283
|
# File 'lib/brief/document.rb', line 281
def structure
@structure_analyzer ||= Brief::Document::Structure.new(fragment, raw_content.lines.to_a)
end
|
#title ⇒ Object
34
35
36
|
# File 'lib/brief/document.rb', line 34
def title
(data && data.title) || css('h1:first-of-type').text || path.to_s.split("/").last.gsub(/\..*/,'')
end
|
#to_model ⇒ Object
226
227
228
|
# File 'lib/brief/document.rb', line 226
def to_model
model_class.try(:new, model_attributes)
end
|
#to_s ⇒ Object
38
39
40
|
# File 'lib/brief/document.rb', line 38
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
303
304
305
306
|
# File 'lib/brief/document.rb', line 303
def transformer_for(doc_fragment=nil)
doc_fragment ||= fragment
@transformer ||= Brief::Document::Transformer.new(doc_fragment, self)
end
|
#type ⇒ Object
312
313
314
|
# File 'lib/brief/document.rb', line 312
def type
document_type
end
|