utopia 2.11.1 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Rakefile +2 -0
  4. data/benchmarks/call_vs_check.rb +2 -0
  5. data/benchmarks/const_vs_hash.rb +2 -0
  6. data/benchmarks/hash_vs_openstruct.rb +2 -0
  7. data/benchmarks/string_vs_symbol.rb +2 -0
  8. data/benchmarks/struct_vs_class.rb +2 -0
  9. data/documentation/Gemfile +1 -0
  10. data/documentation/Rakefile +1 -0
  11. data/documentation/config.ru +1 -0
  12. data/documentation/config/environment.rb +1 -0
  13. data/documentation/pages/wiki/controller.rb +1 -0
  14. data/documentation/spec/website_context.rb +1 -0
  15. data/documentation/spec/website_spec.rb +1 -0
  16. data/lib/utopia.rb +2 -0
  17. data/lib/utopia/command.rb +2 -0
  18. data/lib/utopia/command/environment.rb +2 -0
  19. data/lib/utopia/command/server.rb +2 -0
  20. data/lib/utopia/command/site.rb +2 -0
  21. data/lib/utopia/content.rb +10 -1
  22. data/lib/utopia/content/document.rb +2 -0
  23. data/lib/utopia/content/link.rb +2 -0
  24. data/lib/utopia/content/links.rb +160 -124
  25. data/lib/utopia/content/markup.rb +2 -0
  26. data/lib/utopia/content/namespace.rb +2 -0
  27. data/lib/utopia/content/node.rb +6 -3
  28. data/lib/utopia/content/response.rb +2 -0
  29. data/lib/utopia/content/tags.rb +2 -0
  30. data/lib/utopia/content_length.rb +2 -0
  31. data/lib/utopia/controller.rb +2 -0
  32. data/lib/utopia/controller/actions.rb +2 -0
  33. data/lib/utopia/controller/base.rb +2 -0
  34. data/lib/utopia/controller/respond.rb +2 -0
  35. data/lib/utopia/controller/rewrite.rb +2 -0
  36. data/lib/utopia/controller/variables.rb +2 -0
  37. data/lib/utopia/exceptions.rb +2 -0
  38. data/lib/utopia/exceptions/handler.rb +2 -0
  39. data/lib/utopia/exceptions/mailer.rb +2 -0
  40. data/lib/utopia/extensions/array_split.rb +2 -0
  41. data/lib/utopia/extensions/date_comparisons.rb +2 -0
  42. data/lib/utopia/http.rb +2 -0
  43. data/lib/utopia/locale.rb +2 -0
  44. data/lib/utopia/localization.rb +2 -0
  45. data/lib/utopia/logger.rb +2 -0
  46. data/lib/utopia/middleware.rb +2 -0
  47. data/lib/utopia/path.rb +2 -0
  48. data/lib/utopia/path/matcher.rb +2 -0
  49. data/lib/utopia/redirection.rb +2 -0
  50. data/lib/utopia/session.rb +2 -0
  51. data/lib/utopia/session/lazy_hash.rb +2 -0
  52. data/lib/utopia/session/serialization.rb +2 -0
  53. data/lib/utopia/setup.rb +2 -0
  54. data/lib/utopia/static.rb +2 -0
  55. data/lib/utopia/static/local_file.rb +2 -0
  56. data/lib/utopia/static/mime_types.rb +2 -0
  57. data/lib/utopia/version.rb +3 -1
  58. data/setup/site/Gemfile +1 -0
  59. data/setup/site/Rakefile +1 -0
  60. data/setup/site/config.ru +1 -0
  61. data/setup/site/config/environment.rb +1 -0
  62. data/setup/site/falcon.rb +1 -0
  63. data/setup/site/spec/spec_helper.rb +1 -0
  64. data/setup/site/spec/website_context.rb +1 -0
  65. data/setup/site/spec/website_spec.rb +1 -0
  66. data/setup/site/tasks/deploy.rake +1 -0
  67. data/setup/site/tasks/development.rake +1 -0
  68. data/setup/site/tasks/environment.rake +1 -0
  69. data/setup/site/tasks/log.rake +1 -0
  70. data/setup/site/tasks/static.rake +1 -0
  71. data/setup/site/tasks/yarn.rake +1 -0
  72. data/spec/mock_node.rb +1 -0
  73. data/spec/spec_helper.rb +1 -0
  74. data/spec/utopia/command_spec.rb +2 -0
  75. data/spec/utopia/content/document_spec.rb +2 -0
  76. data/spec/utopia/content/link_spec.rb +1 -0
  77. data/spec/utopia/content/links_spec.rb +21 -21
  78. data/spec/utopia/content/markup_spec.rb +1 -0
  79. data/spec/utopia/content/namespace_spec.rb +2 -0
  80. data/spec/utopia/content/node_spec.rb +2 -0
  81. data/spec/utopia/content/response_spec.rb +2 -0
  82. data/spec/utopia/content/tags_spec.rb +2 -0
  83. data/spec/utopia/content_spec.rb +2 -0
  84. data/spec/utopia/content_spec.ru +1 -0
  85. data/spec/utopia/controller/actions_spec.rb +1 -0
  86. data/spec/utopia/controller/middleware_spec.rb +1 -0
  87. data/spec/utopia/controller/middleware_spec.ru +1 -0
  88. data/spec/utopia/controller/middleware_spec/controller/controller.rb +1 -0
  89. data/spec/utopia/controller/middleware_spec/controller/nested/controller.rb +1 -0
  90. data/spec/utopia/controller/middleware_spec/redirect/controller.rb +1 -0
  91. data/spec/utopia/controller/middleware_spec/redirect/test/controller.rb +1 -0
  92. data/spec/utopia/controller/respond_spec.rb +1 -0
  93. data/spec/utopia/controller/respond_spec.ru +1 -0
  94. data/spec/utopia/controller/respond_spec/api/controller.rb +1 -0
  95. data/spec/utopia/controller/respond_spec/errors/controller.rb +1 -0
  96. data/spec/utopia/controller/respond_spec/html/controller.rb +1 -0
  97. data/spec/utopia/controller/respond_spec/rewrite/controller.rb +1 -0
  98. data/spec/utopia/controller/rewrite_spec.rb +1 -0
  99. data/spec/utopia/controller/sequence_spec.rb +2 -1
  100. data/spec/utopia/controller/variables_spec.rb +1 -0
  101. data/spec/utopia/controller/websocket_spec.rb +1 -0
  102. data/spec/utopia/controller/websocket_spec.ru +1 -0
  103. data/spec/utopia/controller/websocket_spec/server/controller.rb +1 -0
  104. data/spec/utopia/exceptions/handler_spec.rb +1 -0
  105. data/spec/utopia/exceptions/handler_spec.ru +1 -0
  106. data/spec/utopia/exceptions/handler_spec/controller.rb +1 -0
  107. data/spec/utopia/exceptions/mailer_spec.rb +1 -0
  108. data/spec/utopia/exceptions/mailer_spec.ru +1 -0
  109. data/spec/utopia/extensions_spec.rb +2 -0
  110. data/spec/utopia/http/status_spec.rb +2 -0
  111. data/spec/utopia/locale_spec.rb +2 -0
  112. data/spec/utopia/localization_spec.rb +2 -0
  113. data/spec/utopia/localization_spec.ru +1 -0
  114. data/spec/utopia/localization_spec/controller.rb +1 -0
  115. data/spec/utopia/middleware_spec.rb +2 -0
  116. data/spec/utopia/path/matcher_spec.rb +1 -0
  117. data/spec/utopia/path_spec.rb +1 -0
  118. data/spec/utopia/performance_spec.rb +2 -0
  119. data/spec/utopia/performance_spec/config.ru +1 -0
  120. data/spec/utopia/performance_spec/pages/api/controller.rb +1 -0
  121. data/spec/utopia/rack_helper.rb +2 -0
  122. data/spec/utopia/redirection_spec.rb +2 -0
  123. data/spec/utopia/redirection_spec.ru +1 -0
  124. data/spec/utopia/session_spec.rb +2 -0
  125. data/spec/utopia/session_spec.ru +1 -0
  126. data/spec/utopia/setup_spec.rb +2 -0
  127. data/spec/utopia/static_spec.rb +2 -0
  128. data/spec/utopia/static_spec.ru +1 -0
  129. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df68f6673051f098f2217495d1e53ef7231ebe5c8708c0b348c55578af4fe3ff
4
- data.tar.gz: 5f89035aa648667e597e9620935568ce3290b3af9bce8302bad9a7540c32f89d
3
+ metadata.gz: 9f1ccbf6d14948ea0dba9dc444126117dcecfec2723bd6bda6278c8cb61e33f2
4
+ data.tar.gz: 5d3bceb49b3e9d08bb7a88720445238b3fd6603afdc2862c918afae33ebd70b0
5
5
  SHA512:
6
- metadata.gz: d09cae540773f16b23ab72451de89a3854c68883f856a7e5de0f6535acf2c4b035f1cb0240f66ae272ab6cf127d0415e1994390e99d5df75f858b670bb2aa902
7
- data.tar.gz: cb7c3e8b7f0b4ae063b4cf2e7bb47914dd6457290a0ea4527d8c1ba54264db749564f4e5fc76e6ecedb7ec69237ec6b10f2191a2a9edfb2ec85f4a866a6b825c
6
+ metadata.gz: 45e93d16075a9f89a49fe3f1797ce2e337fb95abdc5a286b9d85c88320920ce020e14285f80f48bc5c25d7f1b45d5378a05157c7bd7354a9f5b5787a2b5b413f
7
+ data.tar.gz: d9cbdaf061fc4d22e6657318adb33e59d6df1b1b07e93150f705b481e0561a6dfd376c0afbecc3a946203dd9c3b05ae05f6c5b932bc54f6f2c345afc630a1d0c
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in utopia.gemspec
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'benchmark/ips'
2
4
 
3
5
  class A
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'benchmark/ips'
2
4
 
3
5
  module Foo
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'benchmark/ips'
2
4
  require 'ostruct'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'benchmark/ips'
2
4
 
3
5
  STRING_HASH = { "foo" => "bar" }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'benchmark/ips'
2
4
 
3
5
  # This benchmark compares accessing an instance variable vs accessing a struct member (via a function). The actual method dispatch is about 25% slower.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  source "https://rubygems.org"
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'pathname'
3
4
  SITE_ROOT = Pathname.new(__dir__).realpath
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env rackup
2
+ # frozen_string_literal: true
2
3
 
3
4
  require_relative 'config/environment'
4
5
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'bundler/setup'
3
4
  Bundler.setup
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  prepend Actions
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'rack/test'
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require_relative 'website_context'
3
4
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -21,6 +23,7 @@
21
23
  require_relative 'middleware'
22
24
  require_relative 'localization'
23
25
 
26
+ require_relative 'content/links'
24
27
  require_relative 'content/node'
25
28
  require_relative 'content/markup'
26
29
  require_relative 'content/tags'
@@ -46,6 +49,8 @@ module Utopia
46
49
  @template_cache = Concurrent::Map.new
47
50
  @node_cache = Concurrent::Map.new
48
51
 
52
+ @links = Links.new(@root)
53
+
49
54
  @namespaces = namespaces
50
55
 
51
56
  # Default content namespace for dynamic path based lookup:
@@ -67,6 +72,10 @@ module Utopia
67
72
 
68
73
  attr :root
69
74
 
75
+ def links(path, **options)
76
+ @links.index(path, options)
77
+ end
78
+
70
79
  def fetch_template(path)
71
80
  @template_cache.fetch_or_store(path.to_s) do
72
81
  Trenni::MarkupTemplate.load_file(path)
@@ -112,7 +121,7 @@ module Utopia
112
121
  end
113
122
 
114
123
  locale = env[Localization::CURRENT_LOCALE_KEY]
115
- if link = Links.for(@root, path, locale)
124
+ if link = @links.for(path, locale)
116
125
  if link.path and node = lookup_node(link.path)
117
126
  attributes = request.env.fetch(VARIABLES_KEY, {}).to_hash
118
127
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -20,15 +22,35 @@
20
22
 
21
23
  require_relative 'link'
22
24
 
25
+ require 'concurrent/map'
26
+
23
27
  module Utopia
24
28
  class Content
25
29
  # The file extension for markup nodes on disk.
26
30
  XNODE_EXTENSION = '.xnode'.freeze
27
31
 
28
- # Represents a list of {Link} instances relating to the structure of the content. They are formed from the `links.yaml` file and the actual directory structure on disk.
29
32
  class Links
30
33
  def self.for(root, path, locale = nil)
31
- links = self.new(root, path.dirname)
34
+ warn "Using uncached links metadata!"
35
+ self.new(root).for(path, locale)
36
+ end
37
+
38
+ def self.index(root, path, **options)
39
+ warn "Using uncached links metadata!"
40
+ self.new(root).index(path, **options)
41
+ end
42
+
43
+ XNODE_FILTER = /^(.+)#{Regexp.escape XNODE_EXTENSION}$/
44
+ INDEX_XNODE_FILTER = /^(index(\..+)*)#{Regexp.escape XNODE_EXTENSION}$/
45
+
46
+ def initialize(root)
47
+ @root = root
48
+
49
+ @cache = Concurrent::Map.new
50
+ end
51
+
52
+ def for(path, locale = nil)
53
+ links = Resolved.new(self, path.dirname)
32
54
 
33
55
  links.lookup(path.last, locale)
34
56
  end
@@ -42,10 +64,10 @@ module Utopia
42
64
  :display => :display,
43
65
  }
44
66
 
45
- def self.index(root, path, **options)
67
+ def index(path, **options)
46
68
  options = DEFAULT_INDEX_OPTIONS.merge(options)
47
69
 
48
- ordered = self.new(root, path, options).ordered
70
+ ordered = Resolved.new(self, path, options).ordered
49
71
 
50
72
  # This option filters a link based on the display parameter.
51
73
  if display_key = options[:display]
@@ -81,152 +103,166 @@ module Utopia
81
103
  return ordered
82
104
  end
83
105
 
84
- XNODE_FILTER = /^(.+)#{Regexp.escape XNODE_EXTENSION}$/
85
- INDEX_XNODE_FILTER = /^(index(\..+)*)#{Regexp.escape XNODE_EXTENSION}$/
86
- LINKS_YAML = "links.yaml"
87
-
88
- DEFAULT_OPTIONS = {
89
- :directories => true,
90
- :files => true,
91
- :virtuals => true,
92
- :indices => true,
93
- }
106
+ attr :root
94
107
 
95
- def initialize(root, top = Path.root, options = DEFAULT_OPTIONS)
96
- raise ArgumentError.new("top path must be absolute") unless top.absolute?
97
-
98
- @top = top
99
- @options = options
108
+ # Represents a list of {Link} instances relating to the structure of the content. They are formed from the `links.yaml` file and the actual directory structure on disk.
109
+ class Resolved
110
+ DEFAULT_OPTIONS = {
111
+ :directories => true,
112
+ :files => true,
113
+ :virtuals => true,
114
+ :indices => true,
115
+ }
100
116
 
101
- # top.components.first == '', but this isn't a problem here.
102
- @path = File.join(root, top.components)
103
- @metadata = self.class.metadata(@path)
117
+ def initialize(links, top = Path.root, options = DEFAULT_OPTIONS)
118
+ raise ArgumentError.new("top path must be absolute") unless top.absolute?
119
+
120
+ @links = links
121
+
122
+ @top = top
123
+ @options = options
124
+
125
+ # top.components.first == '', but this isn't a problem here.
126
+ @path = File.join(links.root, top.components)
127
+
128
+ @ordered = []
129
+ @named = Hash.new{|h,k| h[k] = []}
130
+
131
+ if File.directory? @path
132
+ @metadata = links.metadata(@path)
133
+
134
+ load_links(@metadata.dup) do |link|
135
+ @ordered << link
136
+ @named[link.name] << link
137
+ end
138
+ else
139
+ @metadata = {}
140
+ end
141
+ end
104
142
 
105
- @ordered = []
106
- @named = Hash.new{|h,k| h[k] = []}
143
+ attr :top
144
+ attr :ordered
145
+ attr :named
107
146
 
108
- if File.directory? @path
109
- load_links(@metadata.dup) do |link|
110
- @ordered << link
111
- @named[link.name] << link
147
+ def each(locale)
148
+ return to_enum(:each, locale) unless block_given?
149
+
150
+ ordered.each do |links|
151
+ yield links.find{|link| link.locale == locale}
112
152
  end
113
153
  end
114
- end
115
-
116
- attr :top
117
- attr :ordered
118
- attr :named
119
-
120
- def each(locale)
121
- return to_enum(:each, locale) unless block_given?
122
154
 
123
- ordered.each do |links|
124
- yield links.find{|link| link.locale == locale}
125
- end
126
- end
127
-
128
- def lookup(name, locale = nil)
129
- # This allows generic links to serve any locale requested.
130
- if links = @named[name]
131
- links.find{|link| link.locale == locale} || links.find{|link| link.locale == nil}
132
- end
133
- end
134
-
135
- private
136
-
137
- def self.symbolize_keys(hash)
138
- # Second level attributes should be symbolic:
139
- hash.each do |key, info|
140
- hash[key] = info.each_with_object({}) { |(k,v),result| result[k.to_sym] = v }
155
+ def lookup(name, locale = nil)
156
+ # This allows generic links to serve any locale requested.
157
+ if links = @named[name]
158
+ links.find{|link| link.locale == locale} || links.find{|link| link.locale == nil}
159
+ end
141
160
  end
142
161
 
143
- return hash
144
- end
145
-
146
- def self.metadata(path)
147
- links_path = File.join(path, LINKS_YAML)
162
+ private
148
163
 
149
- hash = if File.exist?(links_path)
150
- YAML::load_file(links_path) || {}
151
- else
152
- {}
164
+ def indices(path, &block)
165
+ Dir.entries(path).select{|filename| filename.match(INDEX_XNODE_FILTER)}
153
166
  end
154
167
 
155
- return symbolize_keys(hash)
156
- end
157
-
158
- def indices(path, &block)
159
- Dir.entries(path).select{|filename| filename.match(INDEX_XNODE_FILTER)}
160
- end
161
-
162
- def load_indices(name, path, metadata)
163
- directory_metadata = metadata.delete(name) || {}
164
- indices_metadata = Links.metadata(path)
165
-
166
- indices_count = 0
167
-
168
- indices(path).each do |filename|
169
- index_name = File.basename(filename, XNODE_EXTENSION)
170
- # Values in indices_metadata will override values in directory_metadata:
171
- index_metadata = directory_metadata.merge(indices_metadata[index_name] || {})
168
+ def load_indices(name, path, metadata)
169
+ directory_metadata = metadata.delete(name) || {}
170
+ indices_metadata = @links.metadata(path)
172
171
 
173
- directory_link = Link.new(:directory, @top + [name, index_name], index_metadata)
172
+ indices_count = 0
174
173
 
175
- # Merge metadata from foo.en into foo/index.en
176
- if directory_link.locale
177
- localized_key = "#{directory_link.name}.#{directory_link.locale}"
178
- if localized_metadata = metadata.delete(localized_key)
179
- directory_link.info.update(localized_metadata)
174
+ indices(path).each do |filename|
175
+ index_name = File.basename(filename, XNODE_EXTENSION)
176
+ # Values in indices_metadata will override values in directory_metadata:
177
+ index_metadata = directory_metadata.merge(indices_metadata[index_name] || {})
178
+
179
+ directory_link = Link.new(:directory, @top + [name, index_name], index_metadata)
180
+
181
+ # Merge metadata from foo.en into foo/index.en
182
+ if directory_link.locale
183
+ localized_key = "#{directory_link.name}.#{directory_link.locale}"
184
+ if localized_metadata = metadata.delete(localized_key)
185
+ directory_link.info.update(localized_metadata)
186
+ end
180
187
  end
188
+
189
+ yield directory_link
190
+
191
+ indices_count += 1
181
192
  end
182
193
 
183
- yield directory_link
184
-
185
- indices_count += 1
194
+ if indices_count == 0
195
+ # Specify a nil uri if no index could be found for the directory:
196
+ yield Link.new(:directory, top + [name], {:uri => nil}.merge(directory_metadata))
197
+ end
186
198
  end
187
199
 
188
- if indices_count == 0
189
- # Specify a nil uri if no index could be found for the directory:
190
- yield Link.new(:directory, top + [name], {:uri => nil}.merge(directory_metadata))
200
+ def entries(path)
201
+ Dir.entries(path).reject{|filename| filename.match(/^[\._]/)}
202
+ end
203
+
204
+ def load_links(metadata, &block)
205
+ # Load all metadata for a given path:
206
+ metadata = @metadata.dup
207
+
208
+ # Check all entries in the given directory:
209
+ entries(@path).each do |filename|
210
+ path = File.join(@path, filename)
211
+
212
+ # There are two types of filelinks based links:
213
+ # 1/ Named files, e.g. foo.xnode, name=foo
214
+ # 2/ Directories, e.g. bar/index.xnode, name=bar
215
+ if File.directory?(path) and @options[:directories]
216
+ load_indices(filename, path, metadata, &block)
217
+ elsif filename.match(INDEX_XNODE_FILTER) and @options[:indices] == false
218
+ metadata.delete($1) # We don't include indices in the list of pages.
219
+ elsif filename.match(XNODE_FILTER) and @options[:files]
220
+ yield Link.new(:file, @top + $1, metadata.delete($1))
221
+ end
222
+ end
223
+
224
+ if @options[:virtuals]
225
+ # After processing all directory entries, we are left with virtual entries in the metadata:
226
+ metadata.each do |name, info|
227
+ virtual_link = Link.new(:virtual, name, info)
228
+
229
+ # Given a virtual named such as "welcome.cn", merge it with metadata from "welcome" if it exists:
230
+ if virtual_metadata = @metadata[virtual_link.name]
231
+ virtual_link.info.update(virtual_metadata)
232
+ end
233
+
234
+ yield virtual_link
235
+ end
236
+ end
191
237
  end
192
238
  end
193
239
 
194
- def entries(path)
195
- Dir.entries(path).reject{|filename| filename.match(/^[\._]/)}
240
+ def metadata(path)
241
+ @cache.fetch_or_store(path.to_s) do
242
+ load(path).freeze
243
+ end
196
244
  end
197
245
 
198
- def load_links(metadata, &block)
199
- # Load all metadata for a given path:
200
- metadata = @metadata.dup
201
-
202
- # Check all entries in the given directory:
203
- entries(@path).each do |filename|
204
- path = File.join(@path, filename)
205
-
206
- # There are two types of filesystem based links:
207
- # 1/ Named files, e.g. foo.xnode, name=foo
208
- # 2/ Directories, e.g. bar/index.xnode, name=bar
209
- if File.directory?(path) and @options[:directories]
210
- load_indices(filename, path, metadata, &block)
211
- elsif filename.match(INDEX_XNODE_FILTER) and @options[:indices] == false
212
- metadata.delete($1) # We don't include indices in the list of pages.
213
- elsif filename.match(XNODE_FILTER) and @options[:files]
214
- yield Link.new(:file, @top + $1, metadata.delete($1))
215
- end
246
+ private
247
+
248
+ def symbolize_keys(hash)
249
+ # Second level attributes should be symbolic:
250
+ hash.each do |key, info|
251
+ hash[key] = info.each_with_object({}) { |(k,v),result| result[k.to_sym] = v }
216
252
  end
217
253
 
218
- if @options[:virtuals]
219
- # After processing all directory entries, we are left with virtual entries in the metadata:
220
- metadata.each do |name, info|
221
- virtual_link = Link.new(:virtual, name, info)
222
-
223
- # Given a virtual named such as "welcome.cn", merge it with metadata from "welcome" if it exists:
224
- if virtual_metadata = @metadata[virtual_link.name]
225
- virtual_link.info.update(virtual_metadata)
226
- end
227
-
228
- yield virtual_link
229
- end
254
+ return hash
255
+ end
256
+
257
+ LINKS_YAML = "links.yaml"
258
+
259
+ def load(path)
260
+ yaml_path = File.join(path, LINKS_YAML)
261
+
262
+ if File.exist?(yaml_path) && data = YAML::load_file(yaml_path)
263
+ return symbolize_keys(data)
264
+ else
265
+ return {}
230
266
  end
231
267
  end
232
268
  end