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
330
331
332
333
334
335
336
|
# File 'lib/brief/document.rb', line 330
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
204
205
206
|
# File 'lib/brief/document.rb', line 204
def at(*args, &block)
parser.send(:at, *args, &block)
end
|
#attachments ⇒ Object
133
134
135
|
# File 'lib/brief/document.rb', line 133
def attachments
Array(data.attachments)
end
|
#briefcase ⇒ Object
147
148
149
|
# File 'lib/brief/document.rb', line 147
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
120
121
122
123
|
# File 'lib/brief/document.rb', line 120
def combined_data_and_content
return content if data.nil? || data.empty?
data.to_hash.to_yaml + "---\n\n#{ content }"
end
|
#content_hash ⇒ Object
59
60
61
|
# File 'lib/brief/document.rb', line 59
def content_hash
Digest::MD5.hexdigest(@content.to_s)
end
|
#content_stale? ⇒ Boolean
67
68
69
|
# File 'lib/brief/document.rb', line 67
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
199
200
201
|
# File 'lib/brief/document.rb', line 199
def css(*args, &block)
parser.send(:css, *args, &block)
end
|
#data ⇒ Object
125
126
127
|
# File 'lib/brief/document.rb', line 125
def data
frontmatter
end
|
#document ⇒ Object
31
32
33
|
# File 'lib/brief/document.rb', line 31
def document
self
end
|
#document_type ⇒ Object
263
264
265
|
# File 'lib/brief/document.rb', line 263
def document_type
options.fetch(:type) { document_type! }
end
|
#document_type! ⇒ Object
267
268
269
270
271
|
# File 'lib/brief/document.rb', line 267
def document_type!
existing = data && data.type
return existing if existing
parent_folder_name.try(:singularize)
end
|
#exist? ⇒ Boolean
244
245
246
|
# File 'lib/brief/document.rb', line 244
def exist?
path && path.exist?
end
|
#extension ⇒ Object
230
231
232
|
# File 'lib/brief/document.rb', line 230
def extension
path.extname
end
|
208
209
210
211
212
213
214
215
216
217
218
219
220
|
# File 'lib/brief/document.rb', line 208
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
63
64
65
|
# File 'lib/brief/document.rb', line 63
def file_hash
Digest::MD5.hexdigest(path.read.to_s)
end
|
#fragment ⇒ Object
322
323
324
|
# File 'lib/brief/document.rb', line 322
def fragment
@fragment ||= Nokogiri::HTML.fragment(to_raw_html)
end
|
#has_sections? ⇒ Boolean
151
152
153
|
# File 'lib/brief/document.rb', line 151
def has_sections?
model_class.section_mappings.length > 0
end
|
#in_briefcase(briefcase) ⇒ Object
137
138
139
140
141
142
143
144
145
|
# File 'lib/brief/document.rb', line 137
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
129
130
131
|
# File 'lib/brief/document.rb', line 129
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
234
235
236
237
238
|
# File 'lib/brief/document.rb', line 234
def model_attributes
(data || {}).to_hash
.merge(path: path, document: self)
.reverse_merge(type: document_type)
end
|
#model_class ⇒ Object
248
249
250
251
252
253
254
255
256
257
258
259
260
261
|
# File 'lib/brief/document.rb', line 248
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.
280
281
282
283
284
|
# File 'lib/brief/document.rb', line 280
def model_instance_registered?
model_class && model_class.models.any? do |model|
model.path == path
end
end
|
#parent_folder_name ⇒ Object
273
274
275
|
# File 'lib/brief/document.rb', line 273
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
301
302
303
304
305
306
307
308
309
|
# File 'lib/brief/document.rb', line 301
def parser
@parser ||= begin
structure.prescan
structure.create_wrappers.tap do |f|
transformer_for(f).all
end
end
end
|
#path_alias ⇒ Object
55
56
57
|
# File 'lib/brief/document.rb', line 55
def path_alias
relative_path.to_s.gsub(relative_path.extname.to_s, '')
end
|
#raw=(val) ⇒ Object
71
72
73
74
75
76
|
# File 'lib/brief/document.rb', line 71
def raw= val
@raw_set = true
@raw_content = val
@raw_content
end
|
#refresh! ⇒ Object
93
94
95
96
97
98
99
100
101
102
|
# File 'lib/brief/document.rb', line 93
def refresh!
@content = nil
@raw_content = path.read
@frontmatter = nil
@raw_frontmatter = nil
@refreshing = true
@content_hash = nil
load_frontmatter
true
end
|
#refreshed ⇒ Object
104
105
106
107
|
# File 'lib/brief/document.rb', line 104
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
222
223
224
225
226
227
228
|
# File 'lib/brief/document.rb', line 222
def relative_path_identifier
if Brief.case
path.relative_path_from(Brief.case.root)
else
path.to_s
end
end
|
#respond_to?(*args) ⇒ Boolean
286
287
288
289
|
# File 'lib/brief/document.rb', line 286
def respond_to?(*args)
method = args.first
super || (data && data.respond_to?(method)) || (data && data.key?(method))
end
|
#save ⇒ Object
82
83
84
85
86
87
88
89
90
91
|
# File 'lib/brief/document.rb', line 82
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
109
110
111
112
113
114
115
116
117
118
|
# File 'lib/brief/document.rb', line 109
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
155
156
157
|
# File 'lib/brief/document.rb', line 155
def section_headings
sections.keys
end
|
#sections ⇒ Object
168
169
170
171
172
173
174
175
176
177
178
179
|
# File 'lib/brief/document.rb', line 168
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
159
160
161
162
163
164
165
166
|
# File 'lib/brief/document.rb', line 159
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
78
79
80
|
# File 'lib/brief/document.rb', line 78
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
295
296
297
|
# File 'lib/brief/document.rb', line 295
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
240
241
242
|
# File 'lib/brief/document.rb', line 240
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
317
318
319
320
|
# File 'lib/brief/document.rb', line 317
def transformer_for(doc_fragment=nil)
doc_fragment ||= fragment
@transformer ||= Brief::Document::Transformer.new(doc_fragment, self)
end
|
#type ⇒ Object
326
327
328
|
# File 'lib/brief/document.rb', line 326
def type
document_type
end
|