Class: Webgen::Node

Inherits:
Object
  • Object
show all
Includes:
Loggable, WebsiteAccess
Defined in:
lib/webgen/node.rb,
lib/webgen/deprecated.rb

Overview

Represents a file, a directory or a fragment. A node always belongs to a Tree.

All needed meta and processing information is associated with a Node. The meta information is available throught the #[] and #meta_info accessors, the processing information through the #node_info accessor.

Although node information should be changed by code, it is not advised to change meta information values in code since this may lead to unwanted behaviour!

Constant Summary collapse

URL_UNSAFE_PATTERN =

This pattern is the the same as URI::UNSAFE except that the hash character (#) is also not escaped. This is needed sothat paths with fragments work correctly.

Regexp.new("[^#{URI::PATTERN::UNRESERVED}#{URI::PATTERN::RESERVED}#]")

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Loggable

#log, #puts

Methods included from WebsiteAccess

included, website

Constructor Details

#initialize(parent, path, cn, meta_info = {}) ⇒ Node

Create a new Node instance.

parent (immutable)

The parent node under which this nodes should be created.

path (immutable)

The full output path for this node. If this node is a directory, the path must have a trailing slash (dir/). If it is a fragment, the hash sign must be the first character of the path (#fragment). This can also be an absolute path like http://myhost.com/.

cn (immutable)

The canonical name for this node. Needs to be of the form basename.ext or basename where basename does not contain any dots. Also, the basename must not include a language part!

meta_info

A hash with meta information for the new node.

The language of a node is taken from the meta information lang and the entry is deleted from the meta information hash. The language cannot be changed afterwards! If no lang key is found, the node is language neutral.



78
79
80
81
82
83
84
# File 'lib/webgen/node.rb', line 78

def initialize(parent, path, cn, meta_info = {})
  @parent = parent
  @cn = cn.freeze
  @children = []
  reinit(path, meta_info)
  init_rest
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object (private)

Delegate missing methods to a processor. The current node is placed into the argument array as the first argument before the method name is invoked on the processor.



380
381
382
383
384
385
386
# File 'lib/webgen/node.rb', line 380

def method_missing(name, *args, &block)
  if node_info[:processor]
    website.cache.instance(node_info[:processor]).send(name, *([self] + args), &block)
  else
    super
  end
end

Instance Attribute Details

#acnObject (readonly)

The absolute canonical name of this node.



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

def acn
  @acn
end

#alcnObject (readonly)

The absolute localized canonical name of this node.



48
49
50
# File 'lib/webgen/node.rb', line 48

def alcn
  @alcn
end

#childrenObject (readonly)

The child nodes of this node.



30
31
32
# File 'lib/webgen/node.rb', line 30

def children
  @children
end

#cnObject (readonly)

The canonical name of this node.



39
40
41
# File 'lib/webgen/node.rb', line 39

def cn
  @cn
end

#langObject (readonly)

The language of this node.



54
55
56
# File 'lib/webgen/node.rb', line 54

def lang
  @lang
end

#lcnObject (readonly)

The localized canonical name of this node.



45
46
47
# File 'lib/webgen/node.rb', line 45

def lcn
  @lcn
end

#levelObject (readonly)

The level of the node. The level specifies how deep the node is in the hierarchy.



51
52
53
# File 'lib/webgen/node.rb', line 51

def level
  @level
end

#meta_infoObject (readonly)

Meta information associated with the node.



57
58
59
# File 'lib/webgen/node.rb', line 57

def meta_info
  @meta_info
end

#parentObject (readonly)

The parent node. This is in all but one case a Node object. The one exception is that the parent of the Tree#dummy_node is a Tree object.



27
28
29
# File 'lib/webgen/node.rb', line 27

def parent
  @parent
end

#pathObject (readonly)

The full output path of this node.



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

def path
  @path
end

#treeObject (readonly)

The tree to which this node belongs.



36
37
38
# File 'lib/webgen/node.rb', line 36

def tree
  @tree
end

Class Method Details

.url(name, make_absolute = true) ⇒ Object

Construct an internal URL for the given name which can be an acn/alcn/path. If the parameter make_absolute is true, then a relative URL will be made absolute by prepending the special URL webgen:://webgen.localhost/.



214
215
216
217
218
# File 'lib/webgen/node.rb', line 214

def self.url(name, make_absolute = true)
  url = URI::parse(URI::escape(name, URL_UNSAFE_PATTERN))
  url = URI::parse('webgen://webgen.localhost/') + url unless url.absolute? || !make_absolute
  url
end

Instance Method Details

#<=>(other) ⇒ Object

Sort nodes by using the meta info sort_info (or title if sort_info is not set) of both involved nodes.



197
198
199
200
201
202
203
204
205
# File 'lib/webgen/node.rb', line 197

def <=>(other)
  self_so = (@meta_info['sort_info'] && @meta_info['sort_info'].to_s) || @meta_info['title'] || ''
  other_so = (other['sort_info'] && other['sort_info'].to_s) || other['title'] || ''
  if self_so !~ /\D/ && other_so !~ /\D/
    self_so = self_so.to_i
    other_so = other_so.to_i
  end
  self_so <=> other_so
end

#=~(pattern) ⇒ Object

Return true if the alcn matches the pattern. See File.fnmatch for useable patterns.



191
192
193
# File 'lib/webgen/node.rb', line 191

def =~(pattern)
  File.fnmatch(pattern, @alcn, File::FNM_DOTMATCH|File::FNM_CASEFOLD|File::FNM_PATHNAME)
end

#[](key) ⇒ Object

Return the meta information item for key.



104
105
106
# File 'lib/webgen/node.rb', line 104

def [](key)
  @meta_info[key]
end

#[]=(key, value) ⇒ Object

Assign value to the meta information item for key.



109
110
111
# File 'lib/webgen/node.rb', line 109

def []=(key, value)
  @meta_info[key] = value
end

#absolute_cnObject



10
11
12
13
# File 'lib/webgen/deprecated.rb', line 10

def absolute_cn
  warn("Deprecation warning (~ #{caller.first}): this method will be removed in one of the next releases - use Node#acn instead!")
  acn
end

#absolute_lcnObject



15
16
17
18
# File 'lib/webgen/deprecated.rb', line 15

def absolute_lcn
  warn("Deprecation warning (~ #{caller.first}): this method will be removed in one of the next releases - use Node#alcn instead!")
  alcn
end

#changed?Boolean

Return true if the node has changed since the last webgen run. If it has changed, dirty is set to true.

Sends the message :node_changed? with self as argument unless the node is already dirty. A listener to this message should set the flag :dirty on the passed node if he thinks it is dirty.

Returns:

  • (Boolean)


162
163
164
165
166
167
168
169
170
# File 'lib/webgen/node.rb', line 162

def changed?
  if_not_checked(:node) do
    flag(:dirty) if meta_info_changed? ||
      node_info[:used_nodes].any? {|n| n != @alcn && (!tree[n] || tree[n].changed?)} ||
      node_info[:used_meta_info_nodes].any? {|n| n != @alcn && (!tree[n] || tree[n].meta_info_changed?)}
    website.blackboard.dispatch_msg(:node_changed?, self) unless flagged?(:dirty)
  end
  flagged?(:dirty)
end

#flag(*keys) ⇒ Object

Flag the node with the keys and dispatch the message :node_flagged with self and keys as arguments. See #flagged for valid keys.



144
145
146
147
# File 'lib/webgen/node.rb', line 144

def flag(*keys)
  @flags += keys
  website.blackboard.dispatch_msg(:node_flagged, self, keys)
end

#flagged(key) ⇒ Object



5
6
7
8
# File 'lib/webgen/deprecated.rb', line 5

def flagged(key)
  warn("Deprecation warning (~ #{caller.first}): this method will be removed in one of the next releases - use Node#flagged? instead!")
  flagged?(key)
end

#flagged?(key) ⇒ Boolean

Check if the node is flagged with one of the following:

:created

Has the node been created or has it been read from the cache?

:reinit

Does the node need to be reinitialized?

:dirty

Set by other objects to true if they think the object has changed since the last run. Must not be set to false once it is true!

:dirty_meta_info

Set by other objects to true if the meta information of the node has changed since the last run. Must not be set to false once it is true!

Returns:

  • (Boolean)


138
139
140
# File 'lib/webgen/node.rb', line 138

def flagged?(key)
  @flags.include?(key)
end

#in_lang(lang) ⇒ Object

Return the node with the same canonical name but in language lang or, if no such node exists, an unlocalized version of the node. If no such node is found either, nil is returned.



232
233
234
235
236
237
238
239
240
241
# File 'lib/webgen/node.rb', line 232

def in_lang(lang)
  avail = @tree.node_access[:acn][@acn]
  avail.find do |n|
    n = n.parent while n.is_fragment?
    n.lang == lang
  end || avail.find do |n|
    n = n.parent while n.is_fragment?
    n.lang.nil?
  end
end

#in_subtree_of?(node) ⇒ Boolean

Check if the this node is in the subtree which is spanned by node. The check is performed using only the parent information of the involved nodes, NOT the actual path/alcn values!

Returns:

  • (Boolean)


223
224
225
226
227
# File 'lib/webgen/node.rb', line 223

def in_subtree_of?(node)
  temp = self
  temp = temp.parent while temp != tree.dummy_root && temp != node
  temp != tree.dummy_root
end

#inspectObject

Return an informative representation of the node.



186
187
188
# File 'lib/webgen/node.rb', line 186

def inspect
  "<##{self.class.name}: alcn=#{@alcn}>"
end

#is_directory?Boolean

Check if the node is a directory.

Returns:

  • (Boolean)


119
# File 'lib/webgen/node.rb', line 119

def is_directory?; @path[-1] == ?/ && !is_fragment?; end

#is_file?Boolean

Check if the node is a file.

Returns:

  • (Boolean)


122
# File 'lib/webgen/node.rb', line 122

def is_file?; !is_directory? && !is_fragment?; end

#is_fragment?Boolean

Check if the node is a fragment.

Returns:

  • (Boolean)


125
# File 'lib/webgen/node.rb', line 125

def is_fragment?; @cn[0] == ?# end

#is_root?Boolean

Check if the node is the root node.

Returns:

  • (Boolean)


128
# File 'lib/webgen/node.rb', line 128

def is_root?; self == tree.root;  end

Return a HTML link from this node to the node or, if this node and node are the same and the parameter website.link_to_current_page is false, a span element with the link text.

You can optionally specify additional attributes for the HTML element in the attr Hash. Also, the meta information link_attrs of the given node is used, if available, to set attributes. However, the attr parameter takes precedence over the link_attrs meta information. Be aware that all key-value pairs with Symbol keys are removed before the attributes are written. Therefore you always need to specify general attributes with Strings!

If the special value :link_text is present in the attributes, it will be used as the link text; otherwise the title of the node will be used.

If the special value :lang is present in the attributes, it will be used as parameter to the node.routing_node call for getting the linked-to node instead of this node’s lang attribute. Note: this is only useful when linking to a directory.



334
335
336
337
338
339
340
341
342
343
344
# File 'lib/webgen/node.rb', line 334

def link_to(node, attr = {})
  attr = node['link_attrs'].merge(attr) if node['link_attrs'].kind_of?(Hash)
  rnode = node.routing_node(attr[:lang] || @lang)
  link_text = attr[:link_text] || (rnode != node && rnode['routed_title']) || node['title']
  attr.delete_if {|k,v| k.kind_of?(Symbol)}

  use_link = (rnode != self || website.config['website.link_to_current_page'])
  attr['href'] = self.route_to(rnode) if use_link
  attrs = attr.collect {|name,value| "#{name.to_s}=\"#{value}\"" }.sort.unshift('').join(' ')
  (use_link ? "<a#{attrs}>#{link_text}</a>" : "<span#{attrs}>#{link_text}</span>")
end

#meta_info_changed?Boolean

Return true if the meta information of the node has changed.

Sends the message :node_meta_info_changed? with self as argument unless the meta information of the node is already dirty. A listener to this message should set the flag :dirt_meta_info on the passed node if he thinks that the node’s meta information is dirty.

Returns:

  • (Boolean)


178
179
180
181
182
183
# File 'lib/webgen/node.rb', line 178

def meta_info_changed?
  if_not_checked(:meta_info) do
    website.blackboard.dispatch_msg(:node_meta_info_changed?, self) unless flagged?(:dirty_meta_info)
  end
  flagged?(:dirty_meta_info)
end

#node_infoObject

Return the node information hash which contains information for processing the node.



114
115
116
# File 'lib/webgen/node.rb', line 114

def node_info
  tree.node_info[@alcn] ||= {}
end

#reinit(path, meta_info = {}) ⇒ Object

Re-initializes an already initialized node and resets it to its pristine state.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/webgen/node.rb', line 87

def reinit(path, meta_info = {})
  old_path = @path if defined?(@path)
  @path = path.freeze
  @lang = Webgen::LanguageManager.language_for_code(meta_info.delete('lang'))
  @lang = nil unless is_file?
  @meta_info = meta_info
  @flags = Set.new([:dirty, :created])
  if defined?(@tree)
    @tree.node_access[:path].delete(old_path) if old_path
    @tree.register_path(self)
    self.node_info.clear
    self.node_info[:used_nodes] = Set.new
    self.node_info[:used_meta_info_nodes] = Set.new
  end
end

#resolve(path, lang = nil, use_passive_sources = true) ⇒ Object

Return the node representing the given path which can be an acn/alcn. The path can be absolute (i.e. starting with a slash) or relative to the current node. If no node exists for the given path or if the path is invalid, nil is returned.

If the path is an alcn and a node is found, it is returned. If the path is an acn, the correct localized node according to lang is returned or if no such node exists but an unlocalized version does, the unlocalized node is returned.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/webgen/node.rb', line 250

def resolve(path, lang = nil, use_passive_sources = true)
  orig_path = path
  url = self.class.url(@alcn) + self.class.url(path, false)

  path = url.path + (url.fragment.nil? ? '' : '#' + url.fragment)
  return nil if path =~ /^\/\.\./

  node = @tree[path, :alcn]
  if !node || node.acn == path
    (node = (@tree[path, :acn] || @tree[path + '/', :acn])) && (node = node.in_lang(lang))
  end
  if !node && use_passive_sources && !website.config['passive_sources'].empty?
    nodes = website.blackboard.invoke(:create_nodes_from_paths, [path])
    node = resolve(orig_path, lang, false)
    node.node_info[:used_meta_info_nodes] += nodes.collect {|n| n.alcn} if node
  end
  node
end

#route_to(other) ⇒ Object

Return the relative path to the given path other. The parameter other can be a Node or a String.



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/webgen/node.rb', line 271

def route_to(other)
  my_url = self.class.url(@path)
  other_url = if other.kind_of?(Node)
                self.class.url(other.routing_node(@lang).path)
              elsif other.kind_of?(String)
                my_url + other
              else
                raise ArgumentError, "improper class for argument"
              end

  # resolve any '.' and '..' paths in the target url
  if other_url.path =~ /\/\.\.?\// && other_url.scheme == 'webgen'
    other_url.path = Pathname.new(other_url.path).cleanpath.to_s
  end
  route = my_url.route_to(other_url).to_s
  (route == '' ? File.basename(self.path) : route)
end

#routing_node(lang, log_warning = true) ⇒ Object

Return the routing node in language lang which is the node that is used when routing to this node. The returned node can differ from the node itself in case of a directory where the routing node is the directory index node. If show_warning is true and this node is a directory node, then a warning is logged if no associated index file is found.



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/webgen/node.rb', line 293

def routing_node(lang, log_warning = true)
  if !is_directory?
    self
  else
    key = [alcn, :index_node, lang]
    vcache = website.cache.volatile
    return vcache[key] if vcache.has_key?(key)

    index_path = self.meta_info['index_path']
    if index_path.nil?
      vcache[key] = self
    else
      index_node = resolve(index_path, lang)
      if index_node
        vcache[key] = index_node
        log(:info) { "Directory index path for <#{alcn}> => <#{index_node.alcn}>" }
      elsif log_warning
        vcache[key] = self
        log(:warn) { "No directory index path found for directory <#{alcn}>" }
      end
    end
    vcache[key] || self
  end
end

#unflag(*keys) ⇒ Object

Remove the flags keys from the node and dispatch the message :node_unflagged with self and keys as arguments.



151
152
153
154
# File 'lib/webgen/node.rb', line 151

def unflag(*keys)
  @flags.subtract(keys)
  website.blackboard.dispatch_msg(:node_unflagged, self, keys)
end