utopia 2.11.1 → 2.12.0

Sign up to get free protection for your applications and to get access to all the features.
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