hx 0.13.0 → 0.14.0

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