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
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
320
321
322
323
324
325
326
|
# File 'lib/brief/document.rb', line 320
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
194
195
196
|
# File 'lib/brief/document.rb', line 194
def at(*args, &block)
parser.send(:at, *args, &block)
end
|
#attachments ⇒ Object
123
124
125
|
# File 'lib/brief/document.rb', line 123
def attachments
Array(data.attachments)
end
|
#briefcase ⇒ Object
137
138
139
|
# File 'lib/brief/document.rb', line 137
def briefcase
(@briefcase_root && Brief.cases[@briefcase_root.basename.to_s]) || Brief.case(true)
end
|
#clone ⇒ Object
34
35
36
|
# File 'lib/brief/document.rb', line 34
def clone
new(path, options)
end
|
#combined_data_and_content ⇒ Object
110
111
112
113
|
# File 'lib/brief/document.rb', line 110
def combined_data_and_content
return content if data.nil? || data.empty?
frontmatter.to_hash.to_yaml + "---\n\n#{ content }"
end
|
#content_hash ⇒ Object
54
55
56
|
# File 'lib/brief/document.rb', line 54
def content_hash
Digest::MD5.hexdigest(@content.to_s)
end
|
#content_stale? ⇒ Boolean
62
63
64
|
# File 'lib/brief/document.rb', line 62
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
189
190
191
|
# File 'lib/brief/document.rb', line 189
def css(*args, &block)
parser.send(:css, *args, &block)
end
|
#data ⇒ Object
115
116
117
|
# File 'lib/brief/document.rb', line 115
def data
frontmatter
end
|
#document ⇒ Object
30
31
32
|
# File 'lib/brief/document.rb', line 30
def document
self
end
|
#document_type ⇒ Object
253
254
255
|
# File 'lib/brief/document.rb', line 253
def document_type
options.fetch(:type) { document_type! }
end
|
#document_type! ⇒ Object
257
258
259
260
261
|
# File 'lib/brief/document.rb', line 257
def document_type!
existing = data && data.type
return existing if existing
parent_folder_name.try(:singularize)
end
|
#exist? ⇒ Boolean
234
235
236
|
# File 'lib/brief/document.rb', line 234
def exist?
path && path.exist?
end
|
#extension ⇒ Object
220
221
222
|
# File 'lib/brief/document.rb', line 220
def extension
path.extname
end
|
198
199
200
201
202
203
204
205
206
207
208
209
210
|
# File 'lib/brief/document.rb', line 198
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
58
59
60
|
# File 'lib/brief/document.rb', line 58
def file_hash
Digest::MD5.hexdigest(path.read.to_s)
end
|
#fragment ⇒ Object
312
313
314
|
# File 'lib/brief/document.rb', line 312
def fragment
@fragment ||= Nokogiri::HTML.fragment(to_raw_html)
end
|
#has_sections? ⇒ Boolean
141
142
143
|
# File 'lib/brief/document.rb', line 141
def has_sections?
model_class.section_mappings.length > 0
end
|
#in_briefcase(briefcase) ⇒ Object
127
128
129
130
131
132
133
134
135
|
# File 'lib/brief/document.rb', line 127
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
119
120
121
|
# File 'lib/brief/document.rb', line 119
def include_attachments?
attachments.length > 0
end
|
#inspect ⇒ Object
46
47
48
|
# File 'lib/brief/document.rb', line 46
def inspect
"#{ model_class }.at_path(#{relative_path})"
end
|
#model_attributes ⇒ Object
224
225
226
227
228
|
# File 'lib/brief/document.rb', line 224
def model_attributes
(data || {}).to_hash
.merge(path: path, document: self)
.reverse_merge(type: document_type)
end
|
#model_class ⇒ Object
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
# File 'lib/brief/document.rb', line 238
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.
270
271
272
273
274
|
# File 'lib/brief/document.rb', line 270
def model_instance_registered?
model_class && model_class.models.any? do |model|
model.path == path
end
end
|
#parent_folder_name ⇒ Object
263
264
265
|
# File 'lib/brief/document.rb', line 263
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
291
292
293
294
295
296
297
298
299
|
# File 'lib/brief/document.rb', line 291
def parser
@parser ||= begin
structure.prescan
structure.create_wrappers.tap do |f|
transformer_for(f).all
end
end
end
|
#raw=(val) ⇒ Object
66
67
68
69
70
71
|
# File 'lib/brief/document.rb', line 66
def raw= val
@raw_set = true
@raw_content = val
@raw_content
end
|
#refresh! ⇒ Object
88
89
90
91
92
93
94
95
96
97
|
# File 'lib/brief/document.rb', line 88
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
50
51
52
|
# File 'lib/brief/document.rb', line 50
def relative_path
briefcase.present? ? path.relative_path_from(briefcase.docs_path) : path
end
|
#relative_path_identifier ⇒ Object
212
213
214
215
216
217
218
|
# File 'lib/brief/document.rb', line 212
def relative_path_identifier
if Brief.case
path.relative_path_from(Brief.case.root)
else
path.to_s
end
end
|
#respond_to?(*args) ⇒ Boolean
276
277
278
279
|
# File 'lib/brief/document.rb', line 276
def respond_to?(*args)
method = args.first
super || (data && data.respond_to?(method)) || (data && data.key?(method))
end
|
#save ⇒ Object
77
78
79
80
81
82
83
84
85
86
|
# File 'lib/brief/document.rb', line 77
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
99
100
101
102
103
104
105
106
107
108
|
# File 'lib/brief/document.rb', line 99
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
145
146
147
|
# File 'lib/brief/document.rb', line 145
def section_headings
sections.keys
end
|
#sections ⇒ Object
158
159
160
161
162
163
164
165
166
167
168
169
|
# File 'lib/brief/document.rb', line 158
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
149
150
151
152
153
154
155
156
|
# File 'lib/brief/document.rb', line 149
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
73
74
75
|
# File 'lib/brief/document.rb', line 73
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
285
286
287
|
# File 'lib/brief/document.rb', line 285
def structure
@structure_analyzer ||= Brief::Document::Structure.new(fragment, raw_content.lines.to_a)
end
|
#title ⇒ Object
38
39
40
|
# File 'lib/brief/document.rb', line 38
def title
(data && data.title) || css('h1:first-of-type').text || path.to_s.split("/").last.gsub(/\..*/,'')
end
|
#to_model ⇒ Object
230
231
232
|
# File 'lib/brief/document.rb', line 230
def to_model
model_class.try(:new, model_attributes)
end
|
#to_s ⇒ Object
42
43
44
|
# File 'lib/brief/document.rb', line 42
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
307
308
309
310
|
# File 'lib/brief/document.rb', line 307
def transformer_for(doc_fragment=nil)
doc_fragment ||= fragment
@transformer ||= Brief::Document::Transformer.new(doc_fragment, self)
end
|
#type ⇒ Object
316
317
318
|
# File 'lib/brief/document.rb', line 316
def type
document_type
end
|