Class: Ro::Node
Instance Attribute Summary collapse
-
#metadata_file ⇒ Object
readonly
Returns the value of attribute metadata_file.
-
#path ⇒ Object
readonly
Returns the value of attribute path.
-
#root ⇒ Object
readonly
Returns the value of attribute root.
Instance Method Summary collapse
- #<=>(other) ⇒ Object
- #[](*args) ⇒ Object
-
#_ignored_files ⇒ Object
T028: Updated ignore patterns for new structure.
- #_load_asset_attributes ⇒ Object
-
#_load_base_attributes ⇒ Object
T026: Modified to load from external metadata_file (new structure).
- #_load_file_attributes ⇒ Object
- #_load_meta_attributes ⇒ Object
- #_render(file) ⇒ Object
- #_render_context ⇒ Object
- #as_json ⇒ Object
-
#asset_dir ⇒ Object
T027: Updated to return assets/ subdirectory in both old and new structure.
- #asset_for(*args) ⇒ Object
- #asset_for?(*args, &block) ⇒ Boolean
- #asset_paths ⇒ Object
- #asset_urls ⇒ Object
- #assets ⇒ Object
- #attributes ⇒ Object
- #collection ⇒ Object
- #created_at ⇒ Object
- #default_sort_key ⇒ Object
- #fetch(*args) ⇒ Object
- #files ⇒ Object
- #get(*args) ⇒ Object
- #id ⇒ Object
- #identifier ⇒ Object
-
#initialize(collection_or_path, metadata_file = nil) ⇒ Node
constructor
T023: Updated to accept (collection, metadata_file) for new structure.
- #inspect ⇒ Object
- #load_attributes ⇒ Object
- #load_attributes! ⇒ Object
- #method_missing(method, *args, &block) ⇒ Object
- #name ⇒ Object
- #path_for ⇒ Object
- #relative_path ⇒ Object
- #sort_key ⇒ Object
- #src_for(*args) ⇒ Object
- #to_hash ⇒ Object
- #to_json ⇒ Object
- #to_s ⇒ Object
- #to_str ⇒ Object
- #to_yaml ⇒ Object
- #type ⇒ Object
- #update_attributes!(attrs = {}, **context) ⇒ Object
- #updated_at ⇒ Object
- #url_for(relative_path, options = {}) ⇒ Object
- #urls ⇒ Object
Methods included from Klass
Constructor Details
#initialize(collection_or_path, metadata_file = nil) ⇒ Node
T023: Updated to accept (collection, metadata_file) for new structure
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/ro/node.rb', line 8 def initialize(collection_or_path, = nil) if # New structure: collection + metadata_file @collection = collection_or_path = Path.for() # Raise error if metadata file doesn't exist unless .exist? raise Errno::ENOENT, "No such file or directory - #{@metadata_file}" end @root = @collection.root # Derive node ID from metadata filename (without extension) # T025: ID derived from metadata filename node_id = .basename.to_s.sub(/\.(yml|yaml|json|toml)$/, '') # Path is the node directory (sibling to metadata file) @path = @collection.path.join(node_id) else # Old structure compatibility: just a path @path = Path.for(collection_or_path) @root = Root.for(@path.parent.parent) = nil end @attributes = :lazyload end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args, &block) ⇒ Object
319 320 321 322 323 324 325 326 327 |
# File 'lib/ro/node.rb', line 319 def method_missing(method, *args, &block) key = method.to_s if attributes.has_key?(key) attributes[key] else super end end |
Instance Attribute Details
#metadata_file ⇒ Object (readonly)
Returns the value of attribute metadata_file.
5 6 7 |
# File 'lib/ro/node.rb', line 5 def end |
#path ⇒ Object (readonly)
Returns the value of attribute path.
5 6 7 |
# File 'lib/ro/node.rb', line 5 def path @path end |
#root ⇒ Object (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
361 362 363 |
# File 'lib/ro/node.rb', line 361 def <=>(other) sort_key <=> other.sort_key end |
#[](*args) ⇒ Object
245 246 247 |
# File 'lib/ro/node.rb', line 245 def [](*args) attributes.get(*args) end |
#_ignored_files ⇒ Object
T028: Updated ignore patterns for new structure
198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/ro/node.rb', line 198 def _ignored_files # Both old and new structure: ignore attributes files and assets/ subdirectory ignored_files = %w[ attributes.yml attributes.yaml attributes.json ./assets/**/** ].map do |glob| @path.glob(glob).select(&:file?) end.flatten end |
#_load_asset_attributes ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/ro/node.rb', line 110 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_attributes ⇒ Object
T026: Modified to load from external metadata_file (new structure)
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/ro/node.rb', line 87 def _load_base_attributes # Start with collection-level metadata (new structure) if it exists base_attrs = Map.new if && .exist? # New structure: load from explicit metadata file attrs = _render() base_attrs = Map.for(attrs) end # Then merge in nested attributes.yml (old structure) if it exists # "Deeper more specific wins" - nested attributes override collection-level glob = "attributes.{yml,yaml,json}" @path.glob(glob) do |file| nested_attrs = _render(file) # Use Map's smart merge: base.apply(override) means override wins base_attrs = base_attrs.apply(Map.for(nested_attrs)) end # Update with the merged result update_attributes!(base_attrs.to_hash, file: || @path) if base_attrs.any? end |
#_load_file_attributes ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/ro/node.rb', line 144 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_attributes ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/ro/node.rb', line 129 def {}.tap do |hash| hash.update( identifier:, type:, id:, urls:, created_at:, updated_at:, ) @attributes.set(_meta: hash) end end |
#_render(file) ⇒ Object
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/ro/node.rb', line 211 def _render(file) node = self value = Ro.render(file, _render_context) if value.is_a?(HTML) front_matter = value.front_matter html = Ro.(value, node) value = HTML.new(html, front_matter:) end if value.is_a?(Hash) attributes = value value = Ro.(attributes, node) end value end |
#_render_context ⇒ Object
230 231 232 233 234 235 |
# File 'lib/ro/node.rb', line 230 def _render_context to_hash.tap do |context| context[:ro] ||= root context[:collection] ||= collection end end |
#as_json ⇒ Object
345 346 347 |
# File 'lib/ro/node.rb', line 345 def as_json(...) to_hash.as_json(...) end |
#asset_dir ⇒ Object
T027: Updated to return assets/ subdirectory in both old and new structure
254 255 256 257 258 |
# File 'lib/ro/node.rb', line 254 def asset_dir # Both old and new structure use assets/ subdirectory # This prevents files from being rendered as templates path.join('assets') end |
#asset_for(*args) ⇒ Object
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/ro/node.rb', line 272 def asset_for(*args) = Map.(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
300 301 302 303 304 |
# File 'lib/ro/node.rb', line 300 def asset_for?(*args, &block) asset_for(*args, &block) rescue StandardError nil end |
#asset_paths ⇒ Object
260 261 262 |
# File 'lib/ro/node.rb', line 260 def asset_paths asset_dir.select { |entry| entry.file? }.sort end |
#asset_urls ⇒ Object
268 269 270 |
# File 'lib/ro/node.rb', line 268 def asset_urls assets.map(&:url) end |
#assets ⇒ Object
264 265 266 |
# File 'lib/ro/node.rb', line 264 def assets asset_paths.map { |path| Asset.for(path, node: self) } end |
#attributes ⇒ Object
66 67 68 69 |
# File 'lib/ro/node.rb', line 66 def attributes load_attributes @attributes end |
#collection ⇒ Object
62 63 64 |
# File 'lib/ro/node.rb', line 62 def collection @collection || @root.collection_for(type) end |
#created_at ⇒ Object
377 378 379 |
# File 'lib/ro/node.rb', line 377 def created_at files.map{|file| File.stat(file).ctime}.min end |
#default_sort_key ⇒ Object
369 370 371 372 373 374 375 |
# File 'lib/ro/node.rb', line 369 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
237 238 239 |
# File 'lib/ro/node.rb', line 237 def fetch(*args) attributes.fetch(*args) end |
#files ⇒ Object
353 354 355 |
# File 'lib/ro/node.rb', line 353 def files path.glob('**/**').select { |entry| entry.file? }.sort end |
#get(*args) ⇒ Object
241 242 243 |
# File 'lib/ro/node.rb', line 241 def get(*args) attributes.get(*args) end |
#id ⇒ Object
46 47 48 |
# File 'lib/ro/node.rb', line 46 def id name end |
#identifier ⇒ Object
54 55 56 |
# File 'lib/ro/node.rb', line 54 def identifier File.join(type, id) end |
#inspect ⇒ Object
58 59 60 |
# File 'lib/ro/node.rb', line 58 def inspect identifier end |
#load_attributes ⇒ Object
71 72 73 |
# File 'lib/ro/node.rb', line 71 def load_attributes load_attributes! if @attributes == :lazyload end |
#load_attributes! ⇒ Object
75 76 77 78 79 80 81 82 83 84 |
# File 'lib/ro/node.rb', line 75 def load_attributes! @attributes = Map.new _load_base_attributes _load_file_attributes _load_asset_attributes @attributes end |
#name ⇒ Object
37 38 39 40 41 42 43 44 |
# File 'lib/ro/node.rb', line 37 def name if # T025: For new structure, name comes from metadata filename .basename.to_s.sub(/\.(yml|yaml|json|toml)$/, '') else @path.name end end |
#path_for ⇒ Object
310 311 312 |
# File 'lib/ro/node.rb', line 310 def path_for(...) @path.join(...) end |
#relative_path ⇒ Object
249 250 251 |
# File 'lib/ro/node.rb', line 249 def relative_path path.relative_to(root) end |
#sort_key ⇒ Object
365 366 367 |
# File 'lib/ro/node.rb', line 365 def sort_key default_sort_key end |
#src_for(*args) ⇒ Object
314 315 316 317 |
# File 'lib/ro/node.rb', line 314 def src_for(*args) key = Path.relative(:assets, :src, args).split('/') get(key) end |
#to_hash ⇒ Object
329 330 331 |
# File 'lib/ro/node.rb', line 329 def to_hash attributes.to_hash end |
#to_json ⇒ Object
341 342 343 |
# File 'lib/ro/node.rb', line 341 def to_json(...) JSON.pretty_generate(to_hash, ...) end |
#to_s ⇒ Object
333 334 335 |
# File 'lib/ro/node.rb', line 333 def to_s(...) to_json(...) end |
#to_str ⇒ Object
337 338 339 |
# File 'lib/ro/node.rb', line 337 def to_str(...) to_json(...) end |
#to_yaml ⇒ Object
349 350 351 |
# File 'lib/ro/node.rb', line 349 def to_yaml(...) to_hash.to_yaml(...) end |
#type ⇒ Object
50 51 52 |
# File 'lib/ro/node.rb', line 50 def type @path.parent.name end |
#update_attributes!(attrs = {}, **context) ⇒ Object
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/ro/node.rb', line 172 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_at ⇒ Object
381 382 383 |
# File 'lib/ro/node.rb', line 381 def updated_at files.map{|file| File.stat(file).mtime}.max end |
#url_for(relative_path, options = {}) ⇒ Object
306 307 308 |
# File 'lib/ro/node.rb', line 306 def url_for(relative_path, = {}) Ro.url_for(self.relative_path, relative_path, ) end |
#urls ⇒ Object
357 358 359 |
# File 'lib/ro/node.rb', line 357 def urls files.map { |file| url_for(file.relative_to(@path)) }.sort end |