hx 0.13.0 → 0.14.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.
data/README.rdoc CHANGED
@@ -1,7 +1,22 @@
1
1
  = hx
2
2
 
3
- A miniature web site generator.
3
+ A miniature web site generator; a spiritual successor to Hobix.
4
+
5
+ == History
6
+
7
+ == The Concept
8
+
9
+ == Tutorial
10
+
11
+ === A Trivial Static Site
12
+
13
+ options:
14
+ output_dir: site
15
+ output:
16
+ - filter: Hx::Backend::RawFiles
17
+ options:
18
+ entry_dir: static
4
19
 
5
20
  == Copyright
6
21
 
7
- Copyright (c) 2010 MenTaLguY. See LICENSE for details.
22
+ Copyright (c) 2009-2011 MenTaLguY. See LICENSE for details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.0
1
+ 0.14.0
data/lib/hx.rb CHANGED
@@ -220,52 +220,80 @@ class StripPath
220
220
  end
221
221
  end
222
222
 
223
+ def self.cache_scope
224
+ saved_records = Thread.current[:hx_cache_records]
225
+ Thread.current[:hx_cache_records] = {}
226
+ begin
227
+ yield
228
+ ensure
229
+ Thread.current[:hx_cache_records] = saved_records
230
+ end
231
+ end
232
+
223
233
  class Cache
224
234
  include Filter
225
235
 
236
+ class Record
237
+ def initialize
238
+ @paths = nil
239
+ @entries = {}
240
+ end
241
+
242
+ def clear_path(path)
243
+ @paths = nil
244
+ @entries.delete path
245
+ end
246
+
247
+ def get_entry_paths
248
+ @paths || (@paths = yield)
249
+ end
250
+
251
+ def get_entry(path)
252
+ begin
253
+ entry = @entries.fetch(path)
254
+ rescue IndexError
255
+ begin
256
+ entry = yield
257
+ rescue NoSuchEntryError
258
+ entry = nil
259
+ end
260
+ @entries[path] = entry
261
+ end
262
+ raise NoSuchEntryError, path unless entry
263
+ entry
264
+ end
265
+ end
266
+
267
+ attr_reader :input
268
+
226
269
  def initialize(input, options={})
270
+ input = input.input while Cache === input
227
271
  @input = input
228
- @lock = Mutex.new
229
- @entries = nil
230
- @entries_by_path = {}
272
+ end
273
+
274
+ def get_cache_record
275
+ (Thread.current[:hx_cache_records] ||= {})[@input] ||= Record.new
231
276
  end
232
277
 
233
278
  def edit_entry(path, prototype=nil)
234
279
  @input.edit_entry(path, prototype) { |text| yield text }
280
+ get_cache_record.clear_path(path)
235
281
  self
236
282
  end
237
283
 
238
- def each_entry(selector)
239
- entries = nil
240
- @lock.synchronize do
241
- if @entries
242
- entries = @entries
243
- else
244
- entries = []
245
- @input.each_entry(Path::ALL) do |path, entry|
246
- @entries_by_path[path] = entry
247
- entries << [path, entry]
248
- end
249
- @entries = entries
250
- end
251
- end
252
- entries.each do |path, entry|
253
- yield path, entry.dup if selector.accept_path? path
284
+ def each_entry_path(selector)
285
+ get_cache_record.get_entry_paths do
286
+ paths = []
287
+ @input.each_entry_path(Path::ALL) { |path| paths << path }
288
+ paths
289
+ end.each do |path|
290
+ yield path if selector.accept_path? path
254
291
  end
255
292
  self
256
293
  end
257
294
 
258
295
  def get_entry(path)
259
- entry = nil
260
- @lock.synchronize do
261
- if @entries_by_path.has_key? path
262
- entry = @entries_by_path[path]
263
- else
264
- entry = @input.get_entry(path)
265
- @entries_by_path[path] = entry
266
- end
267
- end
268
- return entry.dup
296
+ get_cache_record.get_entry(path) { @input.get_entry(path) }
269
297
  end
270
298
  end
271
299
 
@@ -273,7 +301,7 @@ class Sort
273
301
  include Filter
274
302
 
275
303
  def initialize(input, options)
276
- @input = input
304
+ @input = Cache.new(input)
277
305
  @key_fields = Array(options[:sort_by] || []).map { |f| f.to_s }
278
306
  @reverse = !!options[:reverse]
279
307
  end
@@ -438,8 +466,6 @@ def self.build_source(options, default_input, sources, raw_source)
438
466
  :reverse => raw_source['reverse'])
439
467
  end
440
468
 
441
- source = Cache.new(source) if raw_source['cache']
442
-
443
469
  source
444
470
  end
445
471
 
@@ -452,24 +478,21 @@ class Site
452
478
  class << self
453
479
  private :new
454
480
 
455
- def load_file(config_file)
481
+ def load_file(config_file, option_overrides={})
456
482
  File.open(config_file, 'r') do |stream|
457
- load(stream, config_file)
483
+ load(stream, config_file, option_overrides)
458
484
  end
459
485
  end
460
486
 
461
- def load(io, config_path)
487
+ def load(io, config_file, option_overrides={})
462
488
  raw_config = YAML.load(io)
463
- load_raw(raw_config, config_path)
464
- end
465
-
466
- def load_raw(raw_config, config_path)
467
489
  options = {}
468
- options[:base_dir] = File.dirname(config_path)
490
+ options[:base_dir] = File.dirname(config_file)
469
491
  for key, value in raw_config.fetch('options', {})
470
492
  options[key.intern] = value
471
493
  end
472
- options[:config_file] = config_path
494
+ options[:config_file] = config_file
495
+ options.update(option_overrides)
473
496
 
474
497
  if raw_config.has_key? 'require'
475
498
  for library in raw_config['require']
@@ -478,14 +501,25 @@ class Site
478
501
  end
479
502
 
480
503
  raw_sources_by_name = raw_config.fetch('sources', {})
504
+ raw_outputs = raw_config.fetch('output', [])
481
505
  source_names = raw_sources_by_name.keys
482
506
 
483
507
  # build input dependency graph
484
508
  source_dependencies = {}
509
+ source_count_by_dependency = Hash.new(0)
485
510
  for name, raw_source in raw_sources_by_name
486
511
  raw_source = Hx.expand_chain(raw_source)
487
512
  if raw_source.has_key? 'input'
488
- source_dependencies[name] = raw_source['input']
513
+ input_name = raw_source['input']
514
+ source_dependencies[name] = input_name
515
+ source_count_by_dependency[input_name] += 1
516
+ end
517
+ end
518
+ for raw_output in raw_outputs
519
+ raw_output = Hx.expand_chain(raw_output)
520
+ if raw_output.has_key? 'input'
521
+ input_name = raw_source['input']
522
+ source_count_by_dependency[input_name] += 1
489
523
  end
490
524
  end
491
525
 
@@ -510,12 +544,15 @@ class Site
510
544
  sources = {}
511
545
  for name in depth_first_names
512
546
  raw_source = raw_sources_by_name[name]
513
- sources[name] = Hx.build_source(options, NULL_INPUT, sources,
514
- raw_source)
547
+ source = Hx.build_source(options, NULL_INPUT, sources, raw_source)
548
+ if source_count_by_dependency[name] > 1
549
+ source = Cache.new(source, options)
550
+ end
551
+ sources[name] = source
515
552
  end
516
553
 
517
554
  outputs = []
518
- for raw_output in raw_config.fetch('output', [])
555
+ for raw_output in raw_outputs
519
556
  outputs << Hx.build_source(options, NULL_INPUT, sources, raw_output)
520
557
  end
521
558
 
data/lib/hx/cli.rb CHANGED
@@ -107,9 +107,7 @@ def self.cmd_serve(site, port=nil)
107
107
 
108
108
  # reload the site/config, folding in the new base URL
109
109
  config_file = site.options[:config_file]
110
- raw_config = File.open(config_file, 'r') { |s| YAML.load(s) }
111
- (raw_config['options'] ||= {})['base_url'] = base_url
112
- site = Hx::Site.load_raw(raw_config, config_file)
110
+ site = Hx::Site.load_file(config_file, :base_url => base_url)
113
111
 
114
112
  app = Hx::Rack::Application.new(site, site.options)
115
113
  server.mount('/', ::Rack::Handler::WEBrick, app)
@@ -127,17 +125,19 @@ end
127
125
 
128
126
  def self.do_gen(site, update_only)
129
127
  output_dir = Hx.get_pathname(site.options, :output_dir)
130
- site.each_entry(Path::ALL) do |path, entry|
131
- pathname = output_dir + path
132
- content = entry['content']
133
- if update_only
134
- update_time = entry['updated']
135
- else
136
- update_time = nil
128
+ Hx.cache_scope do
129
+ site.each_entry(Path::ALL) do |path, entry|
130
+ pathname = output_dir + path
131
+ content = entry['content']
132
+ if update_only
133
+ update_time = entry['updated']
134
+ else
135
+ update_time = nil
136
+ end
137
+ written = Hx.refresh_file(pathname, content, update_time,
138
+ entry['executable'])
139
+ puts "===> #{path}" if written
137
140
  end
138
- written = Hx.refresh_file(pathname, content, update_time,
139
- entry['executable'])
140
- puts "===> #{path}" if written
141
141
  end
142
142
  end
143
143
 
@@ -30,7 +30,7 @@ class Paginate
30
30
  include Hx::Filter
31
31
 
32
32
  def initialize(input, options)
33
- @input = input
33
+ @input = Cache.new(input)
34
34
  @page_size = options[:page_size]
35
35
  end
36
36
 
@@ -24,6 +24,7 @@
24
24
  require 'hx'
25
25
  require 'hx/listing/limit'
26
26
  require 'hx/listing/paginate'
27
+ require 'set'
27
28
 
28
29
  module Hx
29
30
  module Listing
@@ -52,28 +53,37 @@ class RecursiveIndex
52
53
  end
53
54
  private :merge_updated
54
55
 
55
- def each_entry(selector)
56
- indexes = Hash.new { |h,k| h[k] = {'items' => []} }
57
- @input.each_entry(Path::ALL) do |path, entry|
56
+ def each_entry_path(selector)
57
+ emitted = Set.new
58
+ @input.each_entry_path(Path::ALL) do |path, entry|
58
59
  components = path.split("/")
59
- if components.last == "index"
60
- index = indexes[path] = entry.merge(indexes[path])
61
- merge_updated(index, entry)
62
- else
63
- until components.empty?
64
- components.pop
65
- index_path = (components + ["index"]).join("/")
66
- index = indexes[index_path]
67
- index['items'] << {'path' => path, 'entry' => entry}
68
- merge_updated(index, entry)
69
- end
60
+ until components.empty?
61
+ components.pop
62
+ index_path = (components + ["index"]).join("/")
63
+ break if emitted.include? index_path
64
+ yield index_path if selector.accept_path? index_path
65
+ emitted.add index_path
70
66
  end
71
67
  end
72
- indexes.each do |path, entry|
73
- yield path, entry if selector.accept_path? path
74
- end
75
68
  self
76
69
  end
70
+
71
+ def get_entry(path)
72
+ raise NoSuchEntryError, path unless path =~ %r!^((?:.*/)?)index$!
73
+ prefix = $1
74
+ selector = Path.parse_pattern("#{prefix}**")
75
+ index = {'items' => []}
76
+ @input.each_entry(selector) do |child_path, entry|
77
+ if child_path == path
78
+ index = entry.merge(index)
79
+ else
80
+ index['items'] << {'path' => child_path, 'entry' => entry}
81
+ end
82
+ merge_updated(index, entry)
83
+ end
84
+ raise NoSuchEntryError, path if index['items'].empty?
85
+ index
86
+ end
77
87
  end
78
88
 
79
89
  end
@@ -40,6 +40,18 @@ class Application
40
40
  end
41
41
 
42
42
  def call(env)
43
+ Hx.cache_scope { _call(env) }
44
+ end
45
+
46
+ def _call(env)
47
+ request_method = env["REQUEST_METHOD"]
48
+
49
+ if request_method != "GET" and request_method != "HEAD"
50
+ return [405, {'Content-Type' => 'text/plain',
51
+ 'Allow' => "GET, HEAD"},
52
+ ["405 Method Not Allowed"]]
53
+ end
54
+
43
55
  path = CGI.unescape(env['PATH_INFO'])
44
56
  path = '/' if path.empty?
45
57
  entry = nil
@@ -83,8 +95,12 @@ class Application
83
95
  effective_path =~ /(\.[^.]+)$/
84
96
  content_type = ::Rack::Mime.mime_type($1 || '')
85
97
  end
86
- content = entry['content'].to_s
87
- [200, {'Content-Type' => content_type}, [content]]
98
+ if request_method != "HEAD"
99
+ content = [entry['content'].to_s]
100
+ else
101
+ content = []
102
+ end
103
+ [200, {'Content-Type' => content_type}, content]
88
104
  else
89
105
  message = "#{env['SCRIPT_NAME']}#{path} not found"
90
106
  [404, {'Content-Type' => "text/plain"}, [message]]
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hx
3
3
  version: !ruby/object:Gem::Version
4
- hash: 43
4
+ hash: 39
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 13
8
+ - 14
9
9
  - 0
10
- version: 0.13.0
10
+ version: 0.14.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - MenTaLguY
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-02 00:00:00 -08:00
18
+ date: 2011-03-04 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency