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 +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
|