Class: Ro::Node

Inherits:
Object show all
Includes:
Klass
Defined in:
lib/ro/node.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Klass

included

Constructor Details

#initialize(path) ⇒ Node



7
8
9
10
11
# File 'lib/ro/node.rb', line 7

def initialize(path)
  @path = Path.for(path)
  @root = Root.for(@path.parent.parent)
  @attributes = :lazyload
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object



272
273
274
275
276
277
278
279
280
# File 'lib/ro/node.rb', line 272

def method_missing(method, *args, &block)
  key = method.to_s

  if attributes.has_key?(key)
    attributes[key]
  else
    super
  end
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



5
6
7
# File 'lib/ro/node.rb', line 5

def path
  @path
end

#rootObject (readonly)

Returns the value of attribute root.



5
6
7
# File 'lib/ro/node.rb', line 5

def root
  @root
end

Instance Method Details

#<=>(other) ⇒ Object



314
315
316
# File 'lib/ro/node.rb', line 314

def <=>(other)
  sort_key <=> other.sort_key
end

#[](*args) ⇒ Object



201
202
203
# File 'lib/ro/node.rb', line 201

def [](*args)
  attributes.get(*args)
end

#_ignored_filesObject



153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/ro/node.rb', line 153

def _ignored_files
  ignored_files =
    %w[
      attributes.yml
      attributes.yaml
      attributes.json
      ./assets/**/**
    ].map do |glob|
      @path.glob(glob).select(&:file?)
    end

  ignored_files.flatten
end

#_load_asset_attributesObject



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/ro/node.rb', line 66

def _load_asset_attributes
  {}.tap do |hash|
    assets.each do |asset|
      key = asset.name
      url = asset.url
      path = asset.path.relative_to(@root)
      src = asset.src
      img = asset.img
      size = asset.size

      value = { url:, path:, size:, img:, src: }

      hash[key] = value
    end

    @attributes.set(assets: hash)
  end
end

#_load_base_attributesObject



57
58
59
60
61
62
63
64
# File 'lib/ro/node.rb', line 57

def _load_base_attributes
  glob = "attributes.{yml,yaml,json}"

  @path.glob(glob) do |file|
    attrs = _render(file)
    update_attributes!(attrs, file:)
  end
end

#_load_file_attributesObject



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/ro/node.rb', line 100

def _load_file_attributes
  ignored = _ignored_files

  @path.files.each do |file|
    next if ignored.include?(file)

    rel = file.relative_to(@path)

    key = rel.parts
    basename = key.pop
    base = basename.split('.', 2).first
    key.push(base)

    value = _render(file)

    if value.is_a?(HTML)
      attrs = value.front_matter
      update_attributes!(attrs, file:)
    end

    if @attributes.has?(key)
      raise Error.new("path=#{ @path.inspect } masks #{ key.inspect } in #{ @attributes.inspect }!")
    end

    @attributes.set(key => value)
  end
end

#_load_meta_attributesObject



85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/ro/node.rb', line 85

def _load_meta_attributes
  {}.tap do |hash|
    hash.update(
      identifier:,
      type:,
      id:,
      urls:,
      created_at:,
      updated_at:,
    )

    @attributes.set(_meta: hash)
  end
end

#_render(file) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/ro/node.rb', line 167

def _render(file)
  node = self

  value = Ro.render(file, _render_context)

  if value.is_a?(HTML)
    front_matter = value.front_matter
    html = Ro.expand_asset_urls(value, node)
    value = HTML.new(html, front_matter:)
  end

  if value.is_a?(Hash)
    attributes = value
    value = Ro.expand_asset_values(attributes, node)
  end

  value
end

#_render_contextObject



186
187
188
189
190
191
# File 'lib/ro/node.rb', line 186

def _render_context
  to_hash.tap do |context|
    context[:ro] ||= root
    context[:collection] ||= collection
  end
end

#as_jsonObject



298
299
300
# File 'lib/ro/node.rb', line 298

def as_json(...)
  to_hash.as_json(...)
end

#asset_dirObject



209
210
211
# File 'lib/ro/node.rb', line 209

def asset_dir
  path.join('assets')
end

#asset_for(*args) ⇒ Object



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/ro/node.rb', line 225

def asset_for(*args)
  options = Map.options_for!(args)

  path_info = Path.relative(args)

  path = @path.join('assets', path_info)

  glob = path_info.gsub(/[_-]/, '[_-]')

  globs =
    [
      @path.call('assets', "#{glob}"),
      @path.call('assets', "#{glob}*"),
      @path.call('assets', "**/#{glob}*")
    ]

  candidates = globs.map { |glob| Dir.glob(glob, ::File::FNM_CASEFOLD) }.flatten.compact.uniq.sort

  case candidates.size
  when 0
    raise ArgumentError, "no asset matching #{globs.inspect}"
  else
    path = candidates.last
  end

  Asset.for(path, node: self)
end

#asset_for?(*args, &block) ⇒ Boolean



253
254
255
256
257
# File 'lib/ro/node.rb', line 253

def asset_for?(*args, &block)
  asset_for(*args, &block)
rescue StandardError
  nil
end

#asset_pathsObject



213
214
215
# File 'lib/ro/node.rb', line 213

def asset_paths
  asset_dir.select { |entry| entry.file? }.sort
end

#asset_urlsObject



221
222
223
# File 'lib/ro/node.rb', line 221

def asset_urls
  assets.map(&:url)
end

#assetsObject



217
218
219
# File 'lib/ro/node.rb', line 217

def assets
  asset_paths.map { |path| Asset.for(path, node: self) }
end

#attributesObject



37
38
39
40
# File 'lib/ro/node.rb', line 37

def attributes
  load_attributes
  @attributes
end

#collectionObject



33
34
35
# File 'lib/ro/node.rb', line 33

def collection
  @root.collection_for(type)
end

#created_atObject



330
331
332
# File 'lib/ro/node.rb', line 330

def created_at
  files.map{|file| File.stat(file).ctime}.min
end

#default_sort_keyObject



322
323
324
325
326
327
328
# File 'lib/ro/node.rb', line 322

def default_sort_key
  position = (attributes[:position] ? Float(attributes[:position]) : 0.0)
  published_at = (attributes[:published_at] ? Time.parse(attributes[:published_at].to_s) : Time.at(0)).utc.iso8601
  created_at = (attributes[:created_at] ? Time.parse(attributes[:created_at].to_s) : Time.at(0)).utc.iso8601

  [position, published_at, created_at, name]
end

#fetch(*args) ⇒ Object



193
194
195
# File 'lib/ro/node.rb', line 193

def fetch(*args)
  attributes.fetch(*args)
end

#filesObject



306
307
308
# File 'lib/ro/node.rb', line 306

def files
  path.glob('**/**').select { |entry| entry.file? }.sort
end

#get(*args) ⇒ Object



197
198
199
# File 'lib/ro/node.rb', line 197

def get(*args)
  attributes.get(*args)
end

#idObject



17
18
19
# File 'lib/ro/node.rb', line 17

def id
  name
end

#identifierObject



25
26
27
# File 'lib/ro/node.rb', line 25

def identifier
  File.join(type, id)
end

#inspectObject



29
30
31
# File 'lib/ro/node.rb', line 29

def inspect
  identifier
end

#load_attributesObject



42
43
44
# File 'lib/ro/node.rb', line 42

def load_attributes
  load_attributes! if @attributes == :lazyload
end

#load_attributes!Object



46
47
48
49
50
51
52
53
54
55
# File 'lib/ro/node.rb', line 46

def load_attributes!
  @attributes = Map.new

  _load_base_attributes
  _load_file_attributes
  _load_asset_attributes
  _load_meta_attributes

  @attributes
end

#nameObject



13
14
15
# File 'lib/ro/node.rb', line 13

def name
  @path.name
end

#path_forObject



263
264
265
# File 'lib/ro/node.rb', line 263

def path_for(...)
  @path.join(...)
end

#relative_pathObject



205
206
207
# File 'lib/ro/node.rb', line 205

def relative_path
  path.relative_to(root)
end

#sort_keyObject



318
319
320
# File 'lib/ro/node.rb', line 318

def sort_key
  default_sort_key
end

#src_for(*args) ⇒ Object



267
268
269
270
# File 'lib/ro/node.rb', line 267

def src_for(*args)
  key = Path.relative(:assets, :src, args).split('/')
  get(key)
end

#to_hashObject



282
283
284
# File 'lib/ro/node.rb', line 282

def to_hash
  attributes.to_hash
end

#to_jsonObject



294
295
296
# File 'lib/ro/node.rb', line 294

def to_json(...)
  JSON.pretty_generate(to_hash, ...)
end

#to_sObject



286
287
288
# File 'lib/ro/node.rb', line 286

def to_s(...)
  to_json(...)
end

#to_strObject



290
291
292
# File 'lib/ro/node.rb', line 290

def to_str(...)
  to_json(...)
end

#to_yamlObject



302
303
304
# File 'lib/ro/node.rb', line 302

def to_yaml(...)
  to_hash.to_yaml(...)
end

#typeObject



21
22
23
# File 'lib/ro/node.rb', line 21

def type
  @path.parent.name
end

#update_attributes!(attrs = {}, **context) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/ro/node.rb', line 128

def update_attributes!(attrs = {}, **context)
  attrs = Map.for(attrs)

  blacklist = %w[
    assets
    _meta
  ]

  blacklist.each do |key|
    if attrs.has_key?(key)
      Ro.error!("#{ key } is blacklisted!", **context)
    end
  end

  keys = @attributes.depth_first_keys

  attrs.depth_first_keys.each do |key|
    if keys.include?(key)
      Ro.error!("#{ attrs.inspect } clobbers #{ @attributes.inspect }!", **context)
    end
  end

  @attributes.update(attrs)
end

#updated_atObject



334
335
336
# File 'lib/ro/node.rb', line 334

def updated_at
  files.map{|file| File.stat(file).mtime}.max
end

#url_for(relative_path, options = {}) ⇒ Object



259
260
261
# File 'lib/ro/node.rb', line 259

def url_for(relative_path, options = {})
  Ro.url_for(self.relative_path, relative_path, options)
end

#urlsObject



310
311
312
# File 'lib/ro/node.rb', line 310

def urls
  files.map { |file| url_for(file.relative_to(@path)) }.sort
end