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 +17 -2
- data/VERSION +1 -1
- data/lib/hx.rb +82 -45
- data/lib/hx/cli.rb +13 -13
- data/lib/hx/listing/paginate.rb +1 -1
- data/lib/hx/listing/recursiveindex.rb +27 -17
- data/lib/hx/rack/application.rb +18 -2
- metadata +4 -4
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)
|
22
|
+
Copyright (c) 2009-2011 MenTaLguY. See LICENSE for details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
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
|
-
|
229
|
-
|
230
|
-
|
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
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
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
|
-
|
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,
|
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(
|
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] =
|
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
|
-
|
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
|
-
|
514
|
-
|
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
|
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
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
|
data/lib/hx/listing/paginate.rb
CHANGED
@@ -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
|
56
|
-
|
57
|
-
@input.
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
data/lib/hx/rack/application.rb
CHANGED
@@ -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
|
-
|
87
|
-
|
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:
|
4
|
+
hash: 39
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 14
|
9
9
|
- 0
|
10
|
-
version: 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-
|
18
|
+
date: 2011-03-04 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|