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
321
322
323
324
325
326
327
|
# File 'lib/brief/document.rb', line 321
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
195
196
197
|
# File 'lib/brief/document.rb', line 195
def at(*args, &block)
parser.send(:at, *args, &block)
end
|
#attachments ⇒ Object
124
125
126
|
# File 'lib/brief/document.rb', line 124
def attachments
Array(data.attachments)
end
|
#briefcase ⇒ Object
138
139
140
|
# File 'lib/brief/document.rb', line 138
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
111
112
113
114
|
# File 'lib/brief/document.rb', line 111
def combined_data_and_content
return content if data.nil? || data.empty?
frontmatter.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
190
191
192
|
# File 'lib/brief/document.rb', line 190
def css(*args, &block)
parser.send(:css, *args, &block)
end
|
#data ⇒ Object
116
117
118
|
# File 'lib/brief/document.rb', line 116
def data
frontmatter
end
|
#document ⇒ Object
31
32
33
|
# File 'lib/brief/document.rb', line 31
def document
self
end
|
#document_type ⇒ Object
254
255
256
|
# File 'lib/brief/document.rb', line 254
def document_type
options.fetch(:type) { document_type! }
end
|
#document_type! ⇒ Object
258
259
260
261
262
|
# File 'lib/brief/document.rb', line 258
def document_type!
existing = data && data.type
return existing if existing
parent_folder_name.try(:singularize)
end
|
#exist? ⇒ Boolean
235
236
237
|
# File 'lib/brief/document.rb', line 235
def exist?
path && path.exist?
end
|
#extension ⇒ Object
221
222
223
|
# File 'lib/brief/document.rb', line 221
def extension
path.extname
end
|
199
200
201
202
203
204
205
206
207
208
209
210
211
|
# File 'lib/brief/document.rb', line 199
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
313
314
315
|
# File 'lib/brief/document.rb', line 313
def fragment
@fragment ||= Nokogiri::HTML.fragment(to_raw_html)
end
|
#has_sections? ⇒ Boolean
142
143
144
|
# File 'lib/brief/document.rb', line 142
def has_sections?
model_class.section_mappings.length > 0
end
|
#in_briefcase(briefcase) ⇒ Object
128
129
130
131
132
133
134
135
136
|
# File 'lib/brief/document.rb', line 128
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
120
121
122
|
# File 'lib/brief/document.rb', line 120
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
225
226
227
228
229
|
# File 'lib/brief/document.rb', line 225
def model_attributes
(data || {}).to_hash
.merge(path: path, document: self)
.reverse_merge(type: document_type)
end
|
#model_class ⇒ Object
239
240
241
242
243
244
245
246
247
248
249
250
251
252
|
# File 'lib/brief/document.rb', line 239
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.
271
272
273
274
275
|
# File 'lib/brief/document.rb', line 271
def model_instance_registered?
model_class && model_class.models.any? do |model|
model.path == path
end
end
|
#parent_folder_name ⇒ Object
264
265
266
|
# File 'lib/brief/document.rb', line 264
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
292
293
294
295
296
297
298
299
300
|
# File 'lib/brief/document.rb', line 292
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
|
#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
213
214
215
216
217
218
219
|
# File 'lib/brief/document.rb', line 213
def relative_path_identifier
if Brief.case
path.relative_path_from(Brief.case.root)
else
path.to_s
end
end
|
#respond_to?(*args) ⇒ Boolean
277
278
279
280
|
# File 'lib/brief/document.rb', line 277
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
100
101
102
103
104
105
106
107
108
109
|
# File 'lib/brief/document.rb', line 100
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
146
147
148
|
# File 'lib/brief/document.rb', line 146
def section_headings
sections.keys
end
|
#sections ⇒ Object
159
160
161
162
163
164
165
166
167
168
169
170
|
# File 'lib/brief/document.rb', line 159
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
150
151
152
153
154
155
156
157
|
# File 'lib/brief/document.rb', line 150
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
286
287
288
|
# File 'lib/brief/document.rb', line 286
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
231
232
233
|
# File 'lib/brief/document.rb', line 231
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
308
309
310
311
|
# File 'lib/brief/document.rb', line 308
def transformer_for(doc_fragment=nil)
doc_fragment ||= fragment
@transformer ||= Brief::Document::Transformer.new(doc_fragment, self)
end
|
#type ⇒ Object
317
318
319
|
# File 'lib/brief/document.rb', line 317
def type
document_type
end
|