class Webgen::ItemTracker
Namespace for all item trackers.
About¶ ↑
This extension manager class is used to track various “items”. Such items can be added as a dependency to a node and later be checked if they have changed. This allows webgen to conditionally render a node.
An item can basically be anything, there only has to be an item tracker extension that knows how to handle it. Each item tracker extension is uniquely identified by its name (e.g. :node_content
, :node_meta_info
, …).
Implementing an item tracker.¶ ↑
An item tracker extension class must respond to the following methods:
- initialize(website)
-
Initializes the extension and provides the website object which can be used to resolve the item ID to the referenced item or item data itself.
- item_id(*item)
-
Return the unique ID for the given item. The returned ID has to be unique for this item tracker extension
- item_data(*item)
-
Return the data for the item so that it can be correctly checked later if it has changed. If data for the given item cannot be computer anymore because the item got invalid, raise an exception.
- item_changed?(item_id, old_data)
-
Return
true
if the item identified by its unique ID has changed. The parameterold_data
contains the last known data of the item. - node_referenced?(item_id, old_data, node_alcn)
-
Return
true
if the node identified bynode_alcn
is referenced in the old data identified by the unique ID. - item_description(item_id, data)
-
Return a string or an array of strings which describe the item (identified by its unique ID) and the given item data. This is used for display purposes and should therefore include a nice, human readable interpretation of the item.
The parameter item
for the methods item_id
and item_data
contains the information needed to identify the item and is depdendent on the specific item tracker extension class. Therefore you need to look at the documentation for an item tracker extension to see what it expects as the item.
Since these methods are invoked multiple times for different items, these methods should have no side effects.
Sample item tracker¶ ↑
The following sample item tracker tracks changes in configuration values. It needs the configuration option name as item.
class ConfigTracker def initialize(website) @website = website end def item_id(config_key) config_key end def item_data(config_key) @website.config[config_key] end def item_changed?(config_key, old_val) @website.config[config_key] != old_val end def node_referenced?(config_key, config_value, node_alcn) false end def item_description(config_key, old_val) "The website configuration option '#{config_key}'" end end website.ext.item_tracker.register ConfigTracker, name: :config
Public Class Methods
Create a new item tracker for the given website.
Webgen::ExtensionManager::new
# File lib/webgen/item_tracker.rb 97 def initialize(website) 98 super() 99 @instances = {} 100 @node_dependencies = Hash.new {|h,k| h[k] = Set.new} 101 @item_data = {} 102 @cached = {:node_dependencies => {}, :item_data => {}} 103 @written_nodes = [] 104 @checked_nodes = Set.new 105 106 @website = website 107 108 @item_changed = {} 109 110 @website.blackboard.add_listener(:website_initialized, 'item_tracker') do 111 @cached = @website.cache[:item_tracker_data] || @cached 112 end 113 114 @website.blackboard.add_listener(:after_tree_populated, 'item_tracker') do |node| 115 @item_data.keys.each do |uid| 116 @item_data[uid] = item_tracker(uid.first).item_data(*uid.last) 117 end 118 end 119 120 @website.blackboard.add_listener(:before_all_nodes_written, 'item_tracker') do 121 # make all used item data from the previous pass current again if applicable and remove 122 # invalid UIDs 123 uids_to_update = @written_nodes.each_with_object(Set.new) do |node, set| 124 next unless @website.tree[node.alcn] 125 set.merge(@node_dependencies[node.alcn]) 126 end 127 uids_to_update.each do |uid| 128 begin 129 @item_data[uid] = item_tracker(uid.first).item_data(*uid.last) 130 rescue Exception 131 @item_data.delete(uid) 132 @cached[:item_data].delete(uid) 133 @written_nodes.each do |node| 134 if @node_dependencies[node.alcn].include?(uid) 135 @node_dependencies[node.alcn].delete(uid) 136 @cached[:node_dependencies][node.alcn].delete(uid) 137 node.node_info[:item_tracker_changed_once] = true 138 end 139 end 140 end 141 end 142 143 @written_nodes = [] 144 @item_changed = {} 145 end 146 147 @website.blackboard.add_listener(:after_node_written, 'item_tracker') do |node| 148 @written_nodes << node 149 end 150 151 @website.blackboard.add_listener(:after_all_nodes_written, 'item_tracker') do 152 # update cached data with data from the run 153 uids_to_update = Set.new 154 @written_nodes.each do |node| 155 next unless @website.tree[node.alcn] 156 node.node_info.delete(:item_tracker_changed_once) 157 @cached[:node_dependencies][node.alcn] = @node_dependencies[node.alcn] 158 uids_to_update.merge(@node_dependencies[node.alcn]) 159 end 160 uids_to_update.each {|uid| @cached[:item_data][uid] = @item_data[uid]} 161 end 162 163 @website.blackboard.add_listener(:website_generated, 'item_tracker') do 164 @cached[:node_dependencies].reject! {|alcn, data| !@website.tree[alcn]} 165 166 used_uids = @cached[:node_dependencies].each_with_object(Set.new) {|(_, uids), obj| obj.merge(uids)} 167 @cached[:item_data].merge!(@item_data) 168 @cached[:item_data].reject! {|uid, _| !used_uids.include?(uid)} 169 170 @website.cache[:item_tracker_data] = @cached 171 end 172 end
Public Instance Methods
Add the given item that is handled by the item tracker extension name
as a dependency to the node.
# File lib/webgen/item_tracker.rb 201 def add(node, name, *item) 202 uid = unique_id(name, item) 203 @node_dependencies[node.alcn] << uid 204 @item_data[uid] ||= item_tracker(name).item_data(*uid.last) 205 end
Return a hash with mappings from node ALCNs to their used items (items are converted to a human readable representation by using the item_description method).
If the parameter include_default
is set to false
, the default items added to each node so that it is possible to correctly detect changes, are not included.
The cached data, not the current data, is used. So this information is only useful after a website has been generated.
# File lib/webgen/item_tracker.rb 246 def cached_items(include_default=true) 247 @cached[:node_dependencies].each_with_object({}) do |(alcn, uids), h| 248 h[alcn] = uids.sort {|a,b| a.first <=> b.first }.map do |uid| 249 next if !include_default && ((uid.first == :node_meta_info && uid.last.first == alcn )|| 250 (uid.first == :node_content && uid.last == alcn)) 251 item_tracker(uid.first).item_description(uid.last, @cached[:item_data][uid]) 252 end.compact 253 end 254 end
Return true
if the given item that is handled by the item tracker extension name
has changed.
# File lib/webgen/item_tracker.rb 234 def item_changed?(name, *item) 235 item_changed_by_uid?(unique_id(name, item)) 236 end
Return true
if the given node has changed.
# File lib/webgen/item_tracker.rb 208 def node_changed?(node) 209 return false if @checked_nodes.include?(node) 210 @checked_nodes << node 211 node.node_info[:item_tracker_changed_once] || 212 !@cached[:node_dependencies].has_key?(node.alcn) || 213 @cached[:node_dependencies][node.alcn].any? {|uid| item_changed_by_uid?(uid)} 214 ensure 215 @checked_nodes.delete(node) 216 end
Return true
if the given node has been referenced by any item tracker extension.
# File lib/webgen/item_tracker.rb 219 def node_referenced?(node) 220 if (cached = @website.cache.volatile[:item_tracker_referenced_nodes]).nil? 221 cached = @website.cache.volatile[:item_tracker_referenced_nodes] = Set.new 222 @cached[:node_dependencies].each do |alcn, uids| 223 uids.each do |uid| 224 cached.merge(item_tracker(uid.first).referenced_nodes(uid.last, @cached[:item_data][uid]) - [alcn]) 225 end 226 end 227 end 228 229 cached.include?(node.alcn) 230 end
Register an item tracker.
The parameter klass
has to contain the name of the item tracker class or the class object itself. If the class is located under this namespace, only the class name without the hierarchy part is needed, otherwise the full class name including parent module/class names is needed.
Options:¶ ↑
- :name
-
The name for the item tracker class. If not set, it defaults to the snake-case version (i.e. FileSystem → file_system) of the class name (without the hierarchy part). It should only contain letters.
Examples:¶ ↑
item_tracker.register('Node') # registers Webgen::ItemTracker::Node item_tracker.register('::Node') # registers Node !!! item_tracker.register('MyModule::Doit', name: 'infos')
# File lib/webgen/item_tracker.rb 195 def register(klass, options={}, &block) 196 do_register(klass, options, false, &block) 197 end
Private Instance Methods
Return true
if the given item has changed. See add
for a description of the item parameters.
# File lib/webgen/item_tracker.rb 262 def item_changed_by_uid?(uid) 263 return @item_changed[uid] if @item_changed.has_key?(uid) 264 @item_changed[uid] = if !@cached[:item_data].has_key?(uid) 265 true 266 else 267 item_tracker(uid.first).item_changed?(uid.last, @cached[:item_data][uid]) 268 end 269 end
Return the item tracker extension object called name.
# File lib/webgen/item_tracker.rb 278 def item_tracker(name) 279 @instances[name] ||= extension(name).new(@website) 280 end
Return the unique ID for the given item handled by the item tracker extension object specified by name.
# File lib/webgen/item_tracker.rb 273 def unique_id(name, item) 274 [name.to_sym, item_tracker(name).item_id(*item)] 275 end