hx 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/hx.rb +170 -97
- data/lib/hx/backend/couchdb.rb +17 -17
- data/lib/hx/backend/hobix.rb +36 -37
- data/lib/hx/backend/rawfiles.rb +78 -0
- data/lib/hx/commandline.rb +6 -4
- data/lib/hx/listing/limit.rb +4 -4
- data/lib/hx/listing/paginate.rb +4 -4
- data/lib/hx/listing/recursiveindex.rb +6 -6
- data/lib/hx/output/liquidtemplate.rb +30 -18
- data/lib/hx/rack.rb +24 -0
- data/lib/hx/rack/application.rb +97 -0
- data/spec/cache_spec.rb +1 -1
- data/spec/{nullsource_spec.rb → nullinput_spec.rb} +5 -5
- data/spec/overlay_spec.rb +2 -2
- data/spec/pathops_spec.rb +3 -3
- data/spec/rack_spec.rb +71 -0
- data/spec/spec_helper.rb +11 -7
- metadata +9 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.7.0
|
data/lib/hx.rb
CHANGED
@@ -22,6 +22,7 @@
|
|
22
22
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
23
|
|
24
24
|
require 'rubygems'
|
25
|
+
require 'thread'
|
25
26
|
require 'set'
|
26
27
|
require 'pathname'
|
27
28
|
require 'yaml'
|
@@ -36,49 +37,72 @@ end
|
|
36
37
|
class EditingNotSupportedError < RuntimeError
|
37
38
|
end
|
38
39
|
|
39
|
-
module
|
40
|
+
module Filter
|
40
41
|
def edit_entry(path, prototype=nil)
|
41
42
|
raise EditingNotSupportedError, "Editing not supported for #{path}"
|
42
43
|
end
|
43
44
|
|
45
|
+
def each_entry_path
|
46
|
+
each_entry { |path, entry| yield path }
|
47
|
+
end
|
48
|
+
|
44
49
|
def each_entry
|
45
|
-
|
50
|
+
each_entry_path do |path|
|
51
|
+
begin
|
52
|
+
entry = get_entry(path)
|
53
|
+
rescue NoSuchEntryError
|
54
|
+
next # entries may come and go during the enumeration
|
55
|
+
end
|
56
|
+
yield path, entry
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_entry(path)
|
61
|
+
each_entry do |entry_path, entry|
|
62
|
+
return entry if entry_path == path
|
63
|
+
end
|
64
|
+
raise NoSuchEntryError, path
|
46
65
|
end
|
47
66
|
end
|
48
67
|
|
49
|
-
class
|
50
|
-
include
|
68
|
+
class NullInput
|
69
|
+
include Filter
|
51
70
|
|
52
71
|
def each_entry
|
53
72
|
self
|
54
73
|
end
|
55
74
|
end
|
56
75
|
|
57
|
-
|
76
|
+
NULL_INPUT = NullInput.new
|
58
77
|
|
59
78
|
class PathSubset
|
60
|
-
include
|
79
|
+
include Filter
|
61
80
|
|
62
|
-
def initialize(
|
63
|
-
@
|
81
|
+
def initialize(input, options)
|
82
|
+
@input = input
|
64
83
|
@path_filter = Predicate.new(options[:only], options[:except])
|
65
84
|
end
|
66
85
|
|
67
86
|
def edit_entry(path, prototype=nil)
|
68
87
|
if @path_filter.accept? path
|
69
|
-
@
|
88
|
+
@input.edit_entry(path, prototype) { |text| yield text }
|
70
89
|
else
|
71
90
|
raise EditingNotSupportedError, "Editing not supported for #{path}"
|
72
91
|
end
|
73
92
|
self
|
74
93
|
end
|
75
94
|
|
76
|
-
def
|
77
|
-
@
|
78
|
-
yield path
|
95
|
+
def each_entry_path
|
96
|
+
@input.each_entry_path do |path|
|
97
|
+
yield path if @path_filter.accept? path
|
79
98
|
end
|
80
99
|
self
|
81
100
|
end
|
101
|
+
|
102
|
+
def get_entry(path)
|
103
|
+
raise NoSuchEntryError, path unless @path_filter.accept? path
|
104
|
+
@input.get_entry(path)
|
105
|
+
end
|
82
106
|
end
|
83
107
|
|
84
108
|
class PathSubset::Predicate
|
@@ -115,29 +139,39 @@ class PathSubset::Predicate
|
|
115
139
|
end
|
116
140
|
|
117
141
|
class Overlay
|
118
|
-
include
|
142
|
+
include Filter
|
119
143
|
|
120
|
-
def initialize(*
|
121
|
-
@
|
144
|
+
def initialize(*inputs)
|
145
|
+
@inputs = inputs
|
122
146
|
end
|
123
147
|
|
124
|
-
def
|
148
|
+
def each_entry_path
|
125
149
|
seen = Set[]
|
126
|
-
@
|
127
|
-
|
128
|
-
yield path
|
150
|
+
@inputs.each do |input|
|
151
|
+
input.each_entry_path do |path|
|
152
|
+
yield path unless seen.include? path
|
129
153
|
seen.add path
|
130
154
|
end
|
131
155
|
end
|
132
156
|
self
|
133
157
|
end
|
158
|
+
|
159
|
+
def get_entry(path)
|
160
|
+
@inputs.each do |input|
|
161
|
+
begin
|
162
|
+
return input.get_entry(path)
|
163
|
+
rescue NoSuchEntryError
|
164
|
+
end
|
165
|
+
end
|
166
|
+
raise NoSuchEntryError, path
|
167
|
+
end
|
134
168
|
end
|
135
169
|
|
136
170
|
module CircumfixPath
|
137
|
-
include
|
171
|
+
include Filter
|
138
172
|
|
139
|
-
def initialize(
|
140
|
-
@
|
173
|
+
def initialize(input, options)
|
174
|
+
@input = input
|
141
175
|
@prefix = options[:prefix]
|
142
176
|
@suffix = options[:suffix]
|
143
177
|
prefix = Regexp.quote(@prefix.to_s)
|
@@ -161,16 +195,20 @@ class AddPath
|
|
161
195
|
def edit_entry(path, prototype=nil)
|
162
196
|
path = strip_circumfix(path)
|
163
197
|
raise EditingNotSupportedError, "Editing not supported for #{path}" unless path
|
164
|
-
@
|
198
|
+
@input.edit_entry(path, prototype) { |text| yield text }
|
165
199
|
self
|
166
200
|
end
|
167
201
|
|
168
|
-
def
|
169
|
-
@
|
170
|
-
yield add_circumfix(path), entry
|
171
|
-
end
|
202
|
+
def each_entry_path
|
203
|
+
@input.each_entry_path { |path| yield add_circumfix(path) }
|
172
204
|
self
|
173
205
|
end
|
206
|
+
|
207
|
+
def get_entry(path)
|
208
|
+
path = strip_circumfix(path)
|
209
|
+
raise NoSuchEntryError, path unless path
|
210
|
+
@input.get_entry(path)
|
211
|
+
end
|
174
212
|
end
|
175
213
|
|
176
214
|
class StripPath
|
@@ -178,64 +216,89 @@ class StripPath
|
|
178
216
|
|
179
217
|
def edit_entry(path, prototype=nil)
|
180
218
|
path = add_circumfix(path)
|
181
|
-
@
|
219
|
+
@input.edit_entry(path, prototype) { |text| yield text }
|
182
220
|
self
|
183
221
|
end
|
184
222
|
|
185
|
-
def
|
186
|
-
@
|
223
|
+
def each_entry_path
|
224
|
+
@input.each_entry_path do |path|
|
187
225
|
path = strip_circumfix(path)
|
188
|
-
yield path
|
226
|
+
yield path if path
|
189
227
|
end
|
190
228
|
self
|
191
229
|
end
|
230
|
+
|
231
|
+
def get_entry(path)
|
232
|
+
@input.get_entry(add_circumfix(path))
|
233
|
+
end
|
192
234
|
end
|
193
235
|
|
194
236
|
class Cache
|
195
|
-
include
|
237
|
+
include Filter
|
196
238
|
|
197
|
-
def initialize(
|
198
|
-
@
|
239
|
+
def initialize(input, options={})
|
240
|
+
@input = input
|
241
|
+
@lock = Mutex.new
|
199
242
|
@entries = nil
|
243
|
+
@entries_by_path = {}
|
200
244
|
end
|
201
245
|
|
202
246
|
def edit_entry(path, prototype=nil)
|
203
|
-
@
|
247
|
+
@input.edit_entry(path, prototype) { |text| yield text }
|
204
248
|
self
|
205
249
|
end
|
206
250
|
|
207
251
|
def each_entry
|
208
|
-
|
209
|
-
|
210
|
-
@
|
211
|
-
entries
|
252
|
+
entries = nil
|
253
|
+
@lock.synchronize do
|
254
|
+
if @entries
|
255
|
+
entries = @entries
|
256
|
+
else
|
257
|
+
entries = []
|
258
|
+
@input.each_entry do |path, entry|
|
259
|
+
@entries_by_path[path] = entry
|
260
|
+
entries << [path, entry]
|
261
|
+
end
|
262
|
+
@entries = entries
|
212
263
|
end
|
213
|
-
@entries = entries
|
214
264
|
end
|
215
|
-
|
265
|
+
entries.each do |path, entry|
|
216
266
|
yield path, entry.dup
|
217
267
|
end
|
218
268
|
self
|
219
269
|
end
|
270
|
+
|
271
|
+
def get_entry(path)
|
272
|
+
entry = nil
|
273
|
+
@lock.synchronize do
|
274
|
+
if @entries_by_path.has_key? path
|
275
|
+
entry = @entries_by_path[path]
|
276
|
+
else
|
277
|
+
entry = @input.get_entry(path)
|
278
|
+
@entries_by_path[path] = entry
|
279
|
+
end
|
280
|
+
end
|
281
|
+
return entry.dup
|
282
|
+
end
|
220
283
|
end
|
221
284
|
|
222
285
|
class Sort
|
223
|
-
include
|
286
|
+
include Filter
|
224
287
|
|
225
|
-
def initialize(
|
226
|
-
@
|
288
|
+
def initialize(input, options)
|
289
|
+
@input = input
|
227
290
|
@key_fields = Array(options[:sort_by] || []).map { |f| f.to_s }
|
228
291
|
@reverse = !!options[:reverse]
|
229
292
|
end
|
230
293
|
|
231
294
|
def edit_entry(path, prototype=nil)
|
232
|
-
@
|
295
|
+
@input.edit_entry(path, prototype) { |text| yield text }
|
233
296
|
self
|
234
297
|
end
|
235
298
|
|
236
299
|
def each_entry
|
237
300
|
entries = []
|
238
|
-
@
|
301
|
+
@input.each_entry do |path, entry|
|
239
302
|
entries << [path, entry]
|
240
303
|
end
|
241
304
|
unless @key_fields.empty?
|
@@ -249,17 +312,21 @@ class Sort
|
|
249
312
|
end
|
250
313
|
self
|
251
314
|
end
|
315
|
+
|
316
|
+
def get_entry(path)
|
317
|
+
@input.get_entry(path)
|
318
|
+
end
|
252
319
|
end
|
253
320
|
|
254
321
|
Chain = Object.new
|
255
|
-
def Chain.new(
|
322
|
+
def Chain.new(input, options)
|
256
323
|
filters = options[:chain] || []
|
257
324
|
options = options.dup
|
258
325
|
options.delete(:chain) # prevent inheritance
|
259
326
|
for raw_filter in filters
|
260
|
-
|
327
|
+
input = Hx.build_source(options, input, {}, raw_filter)
|
261
328
|
end
|
262
|
-
|
329
|
+
input
|
263
330
|
end
|
264
331
|
|
265
332
|
def self.make_default_title(options, path)
|
@@ -286,8 +353,15 @@ def self.local_require(options, library)
|
|
286
353
|
saved_require_path = $:.dup
|
287
354
|
begin
|
288
355
|
$:.delete(".")
|
289
|
-
|
356
|
+
lib_dir = Hx.get_pathname(options, :lib_dir)
|
357
|
+
if lib_dir.relative?
|
358
|
+
$:.push "./#{lib_dir}"
|
359
|
+
else
|
360
|
+
$:.push lib_dir.to_s
|
361
|
+
end
|
290
362
|
require library
|
363
|
+
rescue LoadError
|
364
|
+
raise
|
291
365
|
ensure
|
292
366
|
$:[0..-1] = saved_require_path
|
293
367
|
end
|
@@ -301,32 +375,32 @@ def self.resolve_constant(qualified_name, root=Object)
|
|
301
375
|
end
|
302
376
|
end
|
303
377
|
|
304
|
-
def self.expand_chain(
|
305
|
-
case
|
378
|
+
def self.expand_chain(raw_input)
|
379
|
+
case raw_input
|
306
380
|
when Array # rewrite array to Hx::Chain
|
307
|
-
return
|
381
|
+
return NULL_INPUT if raw_input.empty?
|
308
382
|
|
309
|
-
filter_defs =
|
383
|
+
filter_defs = raw_input.dup
|
310
384
|
first_filter = filter_defs[0] = filter_defs[0].dup
|
311
385
|
|
312
|
-
|
386
|
+
raw_input = {
|
313
387
|
'filter' => 'Hx::Chain',
|
314
388
|
'options' => {'chain' => filter_defs}
|
315
389
|
}
|
316
390
|
|
317
|
-
if first_filter.has_key? '
|
318
|
-
|
319
|
-
first_filter.delete('
|
391
|
+
if first_filter.has_key? 'input' # use input of first filter for chain
|
392
|
+
raw_input['input'] = first_filter['input']
|
393
|
+
first_filter.delete('input')
|
320
394
|
end
|
321
395
|
end
|
322
|
-
|
396
|
+
raw_input
|
323
397
|
end
|
324
398
|
|
325
399
|
def self.build_source(options, default_input, sources, raw_source)
|
326
400
|
raw_source = expand_chain(raw_source)
|
327
401
|
|
328
|
-
if raw_source.has_key? '
|
329
|
-
input_name = raw_source['
|
402
|
+
if raw_source.has_key? 'input'
|
403
|
+
input_name = raw_source['input']
|
330
404
|
begin
|
331
405
|
source = sources.fetch(input_name)
|
332
406
|
rescue IndexError
|
@@ -383,7 +457,7 @@ def self.build_source(options, default_input, sources, raw_source)
|
|
383
457
|
end
|
384
458
|
|
385
459
|
class Site
|
386
|
-
include
|
460
|
+
include Filter
|
387
461
|
|
388
462
|
attr_reader :options
|
389
463
|
attr_reader :sources
|
@@ -392,6 +466,12 @@ class Site
|
|
392
466
|
class << self
|
393
467
|
private :new
|
394
468
|
|
469
|
+
def load_file(config_file)
|
470
|
+
File.open(config_file, 'r') do |stream|
|
471
|
+
load(stream, config_file)
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
395
475
|
def load(io, config_path)
|
396
476
|
raw_config = YAML.load(io)
|
397
477
|
options = {}
|
@@ -409,16 +489,16 @@ class Site
|
|
409
489
|
raw_sources_by_name = raw_config.fetch('sources', {})
|
410
490
|
source_names = raw_sources_by_name.keys
|
411
491
|
|
412
|
-
# build
|
492
|
+
# build input dependency graph
|
413
493
|
source_dependencies = {}
|
414
494
|
for name, raw_source in raw_sources_by_name
|
415
495
|
raw_source = Hx.expand_chain(raw_source)
|
416
|
-
if raw_source.has_key? '
|
417
|
-
source_dependencies[name] = raw_source['
|
496
|
+
if raw_source.has_key? 'input'
|
497
|
+
source_dependencies[name] = raw_source['input']
|
418
498
|
end
|
419
499
|
end
|
420
500
|
|
421
|
-
# calculate depth for each
|
501
|
+
# calculate depth for each input in the graph
|
422
502
|
source_depths = Hash.new(0)
|
423
503
|
for name in source_names
|
424
504
|
seen = Set[] # for cycle detection
|
@@ -439,13 +519,13 @@ class Site
|
|
439
519
|
sources = {}
|
440
520
|
for name in depth_first_names
|
441
521
|
raw_source = raw_sources_by_name[name]
|
442
|
-
sources[name] = Hx.build_source(options,
|
522
|
+
sources[name] = Hx.build_source(options, NULL_INPUT, sources,
|
443
523
|
raw_source)
|
444
524
|
end
|
445
525
|
|
446
526
|
outputs = []
|
447
527
|
for raw_output in raw_config.fetch('outputs', [])
|
448
|
-
outputs << Hx.build_source(options,
|
528
|
+
outputs << Hx.build_source(options, NULL_INPUT, sources, raw_output)
|
449
529
|
end
|
450
530
|
|
451
531
|
new(options, sources, outputs)
|
@@ -464,52 +544,45 @@ class Site
|
|
464
544
|
self
|
465
545
|
end
|
466
546
|
|
467
|
-
def
|
468
|
-
@combined_output.
|
469
|
-
yield path, entry
|
470
|
-
end
|
547
|
+
def each_entry_path
|
548
|
+
@combined_output.each_entry_path { |path| yield path }
|
471
549
|
self
|
472
550
|
end
|
473
|
-
end
|
474
551
|
|
475
|
-
|
476
|
-
|
477
|
-
@output_dir = Pathname.new(output_dir)
|
478
|
-
end
|
479
|
-
|
480
|
-
def build_file(path, entry)
|
481
|
-
build_file_helper(path, entry, false)
|
552
|
+
def get_entry(path)
|
553
|
+
@combined_output.get_entry(path)
|
482
554
|
end
|
555
|
+
end
|
483
556
|
|
484
|
-
|
485
|
-
|
557
|
+
def self.refresh_file(pathname, content, update_time)
|
558
|
+
begin
|
559
|
+
return false if update_time and update_time < pathname.mtime
|
560
|
+
rescue Errno::ENOENT
|
486
561
|
end
|
562
|
+
write_file(pathname, content)
|
563
|
+
true
|
564
|
+
end
|
487
565
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
dirname = filename.parent
|
493
|
-
dirname.mkpath()
|
494
|
-
filename.open("wb") do |stream|
|
495
|
-
stream.write entry['content'].to_s
|
496
|
-
end
|
497
|
-
self
|
498
|
-
end
|
499
|
-
private :build_file_helper
|
566
|
+
def self.write_file(pathname, content)
|
567
|
+
pathname.parent.mkpath()
|
568
|
+
pathname.open("wb") { |stream| stream << content.to_s }
|
569
|
+
nil
|
500
570
|
end
|
501
571
|
|
502
572
|
class LazyContent
|
503
573
|
def initialize(&block)
|
504
574
|
raise ArgumentError, "No block given" unless block
|
575
|
+
@lock = Mutex.new
|
505
576
|
@content = nil
|
506
577
|
@block = block
|
507
578
|
end
|
508
579
|
|
509
580
|
def to_s
|
510
|
-
|
511
|
-
|
512
|
-
|
581
|
+
@lock.synchronize do
|
582
|
+
if @block
|
583
|
+
@content = @block.call
|
584
|
+
@block = nil
|
585
|
+
end
|
513
586
|
end
|
514
587
|
@content
|
515
588
|
end
|
data/lib/hx/backend/couchdb.rb
CHANGED
@@ -33,7 +33,7 @@ module Hx
|
|
33
33
|
module Backend
|
34
34
|
|
35
35
|
class CouchDB
|
36
|
-
include Hx::
|
36
|
+
include Hx::Filter
|
37
37
|
|
38
38
|
class HTTPError < RuntimeError
|
39
39
|
attr_reader :path
|
@@ -51,7 +51,7 @@ class CouchDB
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
def initialize(
|
54
|
+
def initialize(input, options)
|
55
55
|
couchdb_server = options.fetch(:couchdb_server, "http://localhost:5984/")
|
56
56
|
couchdb_database = options.fetch(:couchdb_database, "entries")
|
57
57
|
uri = URI.parse(couchdb_server)
|
@@ -81,25 +81,25 @@ class CouchDB
|
|
81
81
|
self
|
82
82
|
end
|
83
83
|
|
84
|
-
def
|
84
|
+
def each_entry_path
|
85
85
|
listing = JSON.parse(get_document('_all_docs'))
|
86
|
-
|
87
|
-
path = row['id']
|
88
|
-
begin
|
89
|
-
entry = JSON.parse(get_document(path))
|
90
|
-
rescue HTTPError => e
|
91
|
-
raise e unless e.code == 404
|
92
|
-
# the document may have gone away since we requested the list
|
93
|
-
next
|
94
|
-
end
|
95
|
-
for field in %(created updated)
|
96
|
-
entry[field] = Time.parse(entry[field] || "")
|
97
|
-
end
|
98
|
-
yield path, entry
|
99
|
-
end
|
86
|
+
listing['rows'].each { |row| yield row['id'] }
|
100
87
|
self
|
101
88
|
end
|
102
89
|
|
90
|
+
def get_entry(path)
|
91
|
+
begin
|
92
|
+
entry = JSON.parse(get_document(path))
|
93
|
+
rescue HTTPError => e
|
94
|
+
raise e unless e.code == 404
|
95
|
+
raise Hx::NoSuchEntryError, path
|
96
|
+
end
|
97
|
+
for field in %(created updated)
|
98
|
+
entry[field] = Time.parse(entry[field] || "")
|
99
|
+
end
|
100
|
+
entry
|
101
|
+
end
|
102
|
+
|
103
103
|
private
|
104
104
|
def request_path_for(id)
|
105
105
|
"#{@prefix}#{CGI.escape(id)}"
|
data/lib/hx/backend/hobix.rb
CHANGED
@@ -22,19 +22,19 @@
|
|
22
22
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
23
|
|
24
24
|
require 'rubygems'
|
25
|
-
require 'time'
|
26
|
-
require 'pathname'
|
27
25
|
require 'yaml'
|
28
26
|
require 'hx'
|
27
|
+
require 'hx/backend/rawfiles'
|
29
28
|
|
30
29
|
module Hx
|
31
30
|
module Backend
|
32
31
|
|
33
32
|
class Hobix
|
34
|
-
include Hx::
|
33
|
+
include Hx::Filter
|
35
34
|
|
36
|
-
def initialize(
|
37
|
-
|
35
|
+
def initialize(input, options)
|
36
|
+
files = Hx::Backend::RawFiles.new(input, options)
|
37
|
+
@source = Hx::StripPath.new(files, :suffix => ".yaml")
|
38
38
|
end
|
39
39
|
|
40
40
|
def yaml_repr(value)
|
@@ -43,49 +43,48 @@ class Hobix
|
|
43
43
|
private :yaml_repr
|
44
44
|
|
45
45
|
def edit_entry(path, prototype=nil)
|
46
|
-
|
47
|
-
begin
|
48
|
-
text = entry_filename.read
|
49
|
-
previous_mtime = entry_filename.mtime
|
50
|
-
rescue Errno::ENOENT
|
51
|
-
raise Hx::NoSuchEntryError, path unless prototype
|
46
|
+
if prototype
|
52
47
|
prototype = prototype.dup
|
53
48
|
prototype['content'] = (prototype['content'] || "").dup
|
54
49
|
content = prototype['content']
|
55
50
|
def content.to_yaml_style ; :literal ; end
|
56
51
|
native = YAML::DomainType.new('hobix.com,2004', 'entry', prototype)
|
57
|
-
|
58
|
-
|
52
|
+
prototype = { 'content' => YAML.dump(native) }
|
53
|
+
end
|
54
|
+
@source.edit_entry(path, prototype) do |text|
|
55
|
+
begin
|
56
|
+
previous_mtime = @source.get_entry(path)['updated']
|
57
|
+
rescue Hx::NoSuchEntryError
|
58
|
+
previous_mtime = nil
|
59
|
+
end
|
60
|
+
text = yield text
|
61
|
+
repr = YAML.parse(text)
|
62
|
+
keys = {}
|
63
|
+
repr.value.each_key { |key| keys[key.value] = key }
|
64
|
+
%w(created updated).each { |name| keys[name] ||= yaml_repr(name) }
|
65
|
+
update_time = Time.now
|
66
|
+
update_time_repr = yaml_repr(update_time)
|
67
|
+
previous_mtime ||= update_time
|
68
|
+
previous_mtime_repr = yaml_repr(previous_mtime)
|
69
|
+
repr.add(keys['created'], previous_mtime_repr) unless repr['created']
|
70
|
+
repr.add(keys['updated'], update_time_repr)
|
71
|
+
repr.emit
|
59
72
|
end
|
60
|
-
text = yield text
|
61
|
-
repr = YAML.parse(text)
|
62
|
-
keys = {}
|
63
|
-
repr.value.each_key { |key| keys[key.value] = key }
|
64
|
-
%w(created updated).each { |name| keys[name] ||= yaml_repr(name) }
|
65
|
-
update_time = Time.now
|
66
|
-
update_time_repr = yaml_repr(update_time)
|
67
|
-
previous_mtime ||= update_time
|
68
|
-
previous_mtime_repr = yaml_repr(previous_mtime)
|
69
|
-
repr.add(keys['created'], previous_mtime_repr) unless repr['created']
|
70
|
-
repr.add(keys['updated'], update_time_repr)
|
71
|
-
entry_filename.parent.mkpath()
|
72
|
-
entry_filename.open('w') { |stream| stream << repr.emit }
|
73
73
|
self
|
74
74
|
end
|
75
75
|
|
76
|
-
def
|
77
|
-
|
78
|
-
path = entry_filename.relative_path_from(@entry_dir).to_s
|
79
|
-
path.sub!(/\.yaml$/, '')
|
80
|
-
entry = entry_filename.open('r') do |stream|
|
81
|
-
YAML.load(stream).value
|
82
|
-
end
|
83
|
-
entry['updated'] ||= entry_filename.mtime
|
84
|
-
entry['created'] ||= entry['updated']
|
85
|
-
yield path, entry
|
86
|
-
end
|
76
|
+
def each_entry_path
|
77
|
+
@source.each_entry_path { |path| yield path }
|
87
78
|
self
|
88
79
|
end
|
80
|
+
|
81
|
+
def get_entry(path)
|
82
|
+
raw_entry = @source.get_entry(path)
|
83
|
+
entry = YAML.load(raw_entry['content'].to_s).value
|
84
|
+
entry['updated'] ||= raw_entry['updated']
|
85
|
+
entry['created'] ||= raw_entry['created'] || raw_entry['updated']
|
86
|
+
entry
|
87
|
+
end
|
89
88
|
end
|
90
89
|
|
91
90
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# hx/backend/hobix - Hobix filesystem backend for Hx
|
2
|
+
#
|
3
|
+
# Copyright (c) 2009-2010 MenTaLguY <mental@rydia.net>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
require 'rubygems'
|
25
|
+
require 'pathname'
|
26
|
+
require 'hx'
|
27
|
+
|
28
|
+
module Hx
|
29
|
+
module Backend
|
30
|
+
|
31
|
+
class RawFiles
|
32
|
+
include Hx::Filter
|
33
|
+
|
34
|
+
def initialize(input, options)
|
35
|
+
@entry_dir = Hx.get_pathname(options, :entry_dir)
|
36
|
+
end
|
37
|
+
|
38
|
+
def path_to_pathname(path) ; @entry_dir + path ; end
|
39
|
+
private :path_to_pathname
|
40
|
+
|
41
|
+
def pathname_to_path(pathname)
|
42
|
+
pathname.relative_path_from(@entry_dir).to_s
|
43
|
+
end
|
44
|
+
private :pathname_to_path
|
45
|
+
|
46
|
+
def edit_entry(path, prototype=nil)
|
47
|
+
pathname = path_to_pathname(path)
|
48
|
+
begin
|
49
|
+
body = pathname.read
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
raise NoSuchEntryError, path unless prototype
|
52
|
+
text = prototype['content'].to_s
|
53
|
+
end
|
54
|
+
text = yield text
|
55
|
+
Hx.write_file(pathname, text)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def each_entry_path
|
60
|
+
Pathname.glob(@entry_dir + '**/*') do |pathname|
|
61
|
+
yield pathname_to_path(pathname) if pathname.file?
|
62
|
+
end
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_entry(path)
|
67
|
+
pathname = path_to_pathname(path)
|
68
|
+
begin
|
69
|
+
{ 'updated' => pathname.mtime,
|
70
|
+
'content' => Hx::LazyContent.new { pathname.read } }
|
71
|
+
rescue Errno::ENOENT
|
72
|
+
raise Hx::NoSuchEntryError, path
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
data/lib/hx/commandline.rb
CHANGED
@@ -104,14 +104,16 @@ end
|
|
104
104
|
|
105
105
|
def self.do_gen(site, update_only)
|
106
106
|
output_dir = Hx.get_pathname(site.options, :output_dir)
|
107
|
-
builder = Hx::FileBuilder.new(output_dir.to_s)
|
108
107
|
site.each_entry do |path, entry|
|
109
|
-
|
108
|
+
pathname = output_dir + path
|
109
|
+
content = entry['content']
|
110
110
|
if update_only
|
111
|
-
|
111
|
+
update_time = entry['updated']
|
112
112
|
else
|
113
|
-
|
113
|
+
update_time = nil
|
114
114
|
end
|
115
|
+
written = Hx.refresh_file(pathname, content, update_time)
|
116
|
+
puts "===> #{path}" if written
|
115
117
|
end
|
116
118
|
end
|
117
119
|
|
data/lib/hx/listing/limit.rb
CHANGED
@@ -28,15 +28,15 @@ module Hx
|
|
28
28
|
module Listing
|
29
29
|
|
30
30
|
class Limit
|
31
|
-
include Hx::
|
31
|
+
include Hx::Filter
|
32
32
|
|
33
|
-
def initialize(
|
34
|
-
@
|
33
|
+
def initialize(input, options)
|
34
|
+
@input = input
|
35
35
|
@limit = options[:limit]
|
36
36
|
end
|
37
37
|
|
38
38
|
def each_entry
|
39
|
-
@
|
39
|
+
@input.each_entry do |path, entry|
|
40
40
|
if entry['items']
|
41
41
|
trimmed_entry = entry.dup
|
42
42
|
trimmed_entry['items'] = entry['items'][0...@limit]
|
data/lib/hx/listing/paginate.rb
CHANGED
@@ -28,15 +28,15 @@ module Hx
|
|
28
28
|
module Listing
|
29
29
|
|
30
30
|
class Paginate
|
31
|
-
include Hx::
|
31
|
+
include Hx::Filter
|
32
32
|
|
33
|
-
def initialize(
|
34
|
-
@
|
33
|
+
def initialize(input, options)
|
34
|
+
@input = input
|
35
35
|
@page_size = options[:page_size]
|
36
36
|
end
|
37
37
|
|
38
38
|
def each_entry
|
39
|
-
@
|
39
|
+
@input.each_entry do |index_path, index_entry|
|
40
40
|
items = index_entry['items'] || []
|
41
41
|
if items.empty?
|
42
42
|
index_entry = index_entry.dup
|
@@ -30,10 +30,10 @@ module Hx
|
|
30
30
|
module Listing
|
31
31
|
|
32
32
|
class RecursiveIndex
|
33
|
-
include Hx::
|
33
|
+
include Hx::Filter
|
34
34
|
|
35
|
-
def self.new(
|
36
|
-
listing = super(
|
35
|
+
def self.new(input, options)
|
36
|
+
listing = super(input, options)
|
37
37
|
if options.has_key? :limit
|
38
38
|
listing = Limit.new(listing, :limit => options[:limit])
|
39
39
|
end
|
@@ -43,13 +43,13 @@ class RecursiveIndex
|
|
43
43
|
listing
|
44
44
|
end
|
45
45
|
|
46
|
-
def initialize(
|
47
|
-
@
|
46
|
+
def initialize(input, options)
|
47
|
+
@input = input
|
48
48
|
end
|
49
49
|
|
50
50
|
def each_entry
|
51
51
|
indexes = Hash.new { |h,k| h[k] = {'items' => []} }
|
52
|
-
@
|
52
|
+
@input.each_entry do |path, entry|
|
53
53
|
components = path.split("/")
|
54
54
|
until components.empty?
|
55
55
|
components.pop
|
@@ -32,7 +32,7 @@ module Hx
|
|
32
32
|
module Output
|
33
33
|
|
34
34
|
class LiquidTemplate
|
35
|
-
include Hx::
|
35
|
+
include Hx::Filter
|
36
36
|
|
37
37
|
module TextFilters
|
38
38
|
def textilize(input)
|
@@ -61,8 +61,8 @@ class LiquidTemplate
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
def initialize(
|
65
|
-
@
|
64
|
+
def initialize(input, options)
|
65
|
+
@input = input
|
66
66
|
@options = {}
|
67
67
|
for key, value in options
|
68
68
|
@options[key.to_s] = value
|
@@ -73,30 +73,42 @@ class LiquidTemplate
|
|
73
73
|
Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_dir)
|
74
74
|
Liquid::Template.register_filter(TextFilters)
|
75
75
|
template_file = template_dir + options[:template]
|
76
|
-
@template =
|
76
|
+
@template = Liquid::Template.parse(template_file.read)
|
77
77
|
@extension = options[:extension]
|
78
|
+
@mime_type = options[:mime_type]
|
79
|
+
@strip_extension_re = nil
|
80
|
+
@strip_extension_re = /\.#{Regexp.quote(@extension)}$/ if @extension
|
78
81
|
end
|
79
82
|
|
80
|
-
def
|
81
|
-
@
|
83
|
+
def each_entry_path
|
84
|
+
@input.each_entry_path do |path|
|
82
85
|
unless @extension.nil?
|
83
|
-
|
86
|
+
yield "#{path}.#{@extension}"
|
84
87
|
else
|
85
|
-
|
88
|
+
yield path
|
86
89
|
end
|
87
|
-
output_entry = entry.dup
|
88
|
-
output_entry['content'] = Hx::LazyContent.new do
|
89
|
-
@template.render(
|
90
|
-
'now' => Time.now,
|
91
|
-
'options' => @options,
|
92
|
-
'path' => path,
|
93
|
-
'entry' => entry
|
94
|
-
)
|
95
|
-
end
|
96
|
-
yield output_path, output_entry
|
97
90
|
end
|
98
91
|
self
|
99
92
|
end
|
93
|
+
|
94
|
+
def get_entry(path)
|
95
|
+
path = path.sub(@strip_extension_re, '') if @strip_extension_re
|
96
|
+
entry = @input.get_entry(path)
|
97
|
+
output_entry = {}
|
98
|
+
output_entry['content'] = Hx::LazyContent.new do
|
99
|
+
@template.render(
|
100
|
+
'now' => Time.now,
|
101
|
+
'options' => @options,
|
102
|
+
'path' => path,
|
103
|
+
'entry' => entry
|
104
|
+
)
|
105
|
+
end
|
106
|
+
output_entry['mime_type'] = @mime_type if @mime_type
|
107
|
+
if entry.has_key? 'updated'
|
108
|
+
output_entry['created'] = output_entry['updated'] = entry['updated']
|
109
|
+
end
|
110
|
+
output_entry
|
111
|
+
end
|
100
112
|
end
|
101
113
|
|
102
114
|
end
|
data/lib/hx/rack.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# hx/rack - Rack bits for Hx
|
2
|
+
#
|
3
|
+
# Copyright (c) 2009-2010 MenTaLguY <mental@rydia.net>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
require 'hx/rack/application'
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# hx/rack/application - Rack application to serve an Hx site dynamically
|
2
|
+
#
|
3
|
+
# Copyright (c) 2009-2010 MenTaLguY <mental@rydia.net>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
require 'rubygems'
|
25
|
+
require 'cgi'
|
26
|
+
require 'hx'
|
27
|
+
require 'rack/mime'
|
28
|
+
|
29
|
+
module Hx
|
30
|
+
module Rack
|
31
|
+
|
32
|
+
class Application
|
33
|
+
def self.load_file(config_path)
|
34
|
+
site = Hx::Site.load_file(config_path)
|
35
|
+
new(site, site.options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(input, options)
|
39
|
+
@input = input
|
40
|
+
@index_names = Array(options[:index_names] || ['index.html'])
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(env)
|
44
|
+
path = CGI.unescape(env['PATH_INFO'])
|
45
|
+
path = '/' if path.empty?
|
46
|
+
entry = nil
|
47
|
+
|
48
|
+
has_trailing_slash = (path[-1..-1] == '/')
|
49
|
+
|
50
|
+
# for non-slash-terminated paths, try the path directly
|
51
|
+
unless has_trailing_slash
|
52
|
+
begin
|
53
|
+
effective_path = path[1..-1]
|
54
|
+
entry = @input.get_entry(effective_path)
|
55
|
+
rescue NoSuchEntryError
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# no entry? maybe it's a directory; look for an index entry
|
60
|
+
unless entry
|
61
|
+
path =~ %r(^/(.*?)/?$)
|
62
|
+
prefix = $1
|
63
|
+
prefix = "#{prefix}/" unless prefix.empty?
|
64
|
+
for index_name in @index_names
|
65
|
+
begin
|
66
|
+
effective_path = "#{prefix}#{index_name}"
|
67
|
+
entry = @input.get_entry(effective_path)
|
68
|
+
break
|
69
|
+
rescue NoSuchEntryError
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# directory exists, but missing trailing slash?
|
74
|
+
if entry and not has_trailing_slash
|
75
|
+
return [301, {'Content-Type' => 'text/plain',
|
76
|
+
'Location' => "#{env['SCRIPT_NAME']}#{path}/"},
|
77
|
+
["301 Moved Permanently"]]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if entry
|
82
|
+
mime_type = entry['mime_type']
|
83
|
+
unless mime_type
|
84
|
+
effective_path =~ /(\.[^.]+)$/
|
85
|
+
mime_type = ::Rack::Mime.mime_type($1 || '')
|
86
|
+
end
|
87
|
+
content = entry['content'].to_s
|
88
|
+
[200, {'Content-Type' => mime_type}, [content]]
|
89
|
+
else
|
90
|
+
message = "#{env['SCRIPT_NAME']}#{path} not found"
|
91
|
+
[404, {'Content-Type' => "text/plain"}, [message]]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
data/spec/cache_spec.rb
CHANGED
@@ -2,9 +2,9 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
2
2
|
|
3
3
|
require 'hx'
|
4
4
|
|
5
|
-
describe Hx::
|
5
|
+
describe Hx::NullInput do
|
6
6
|
before(:each) do
|
7
|
-
@null_source = Hx::
|
7
|
+
@null_source = Hx::NullInput.new
|
8
8
|
end
|
9
9
|
|
10
10
|
it "should return itself from each_entry" do
|
@@ -18,8 +18,8 @@ describe Hx::NullSource do
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
describe Hx::
|
22
|
-
it "is an instance of Hx::
|
23
|
-
Hx::
|
21
|
+
describe Hx::NULL_INPUT do
|
22
|
+
it "is an instance of Hx::NullInput" do
|
23
|
+
Hx::NULL_INPUT.should be_an_instance_of(Hx::NullInput)
|
24
24
|
end
|
25
25
|
end
|
data/spec/overlay_spec.rb
CHANGED
@@ -4,10 +4,10 @@ require 'hx'
|
|
4
4
|
|
5
5
|
describe Hx::Overlay do
|
6
6
|
before(:each) do
|
7
|
-
@a =
|
7
|
+
@a = FakeInput.new
|
8
8
|
@a.add_entry('foo', 'foo:A')
|
9
9
|
@a.add_entry('bar', 'bar:A')
|
10
|
-
@b =
|
10
|
+
@b = FakeInput.new
|
11
11
|
@b.add_entry('bar', 'bar:B')
|
12
12
|
@b.add_entry('baz', 'baz:B')
|
13
13
|
@overlay = Hx::Overlay.new(@a, @b)
|
data/spec/pathops_spec.rb
CHANGED
@@ -7,7 +7,7 @@ describe Hx::AddPath do
|
|
7
7
|
before(:each) do
|
8
8
|
@before_paths = Set['foo', 'bar']
|
9
9
|
@after_paths = Set['XXXfooYYY', 'XXXbarYYY']
|
10
|
-
@source =
|
10
|
+
@source = FakeInput.new
|
11
11
|
@source.add_entry('foo', 'FOO')
|
12
12
|
@source.add_entry('bar', 'BAR')
|
13
13
|
@add = Hx::AddPath.new(@source, :prefix => 'XXX', :suffix => 'YYY')
|
@@ -29,7 +29,7 @@ end
|
|
29
29
|
describe Hx::StripPath do
|
30
30
|
before(:each) do @before_paths = Set['XXXfooYYY', 'XXXbarYYY', 'lemur']
|
31
31
|
@after_paths = Set['foo', 'bar']
|
32
|
-
@source =
|
32
|
+
@source = FakeInput.new
|
33
33
|
@source.add_entry('XXXfooYYY', 'FOO')
|
34
34
|
@source.add_entry('XXXbarYYY', 'BAR')
|
35
35
|
@strip = Hx::StripPath.new(@source, :prefix => 'XXX', :suffix => 'YYY')
|
@@ -50,7 +50,7 @@ end
|
|
50
50
|
|
51
51
|
describe Hx::PathSubset do
|
52
52
|
before(:each) do
|
53
|
-
@source =
|
53
|
+
@source = FakeInput.new
|
54
54
|
@all_paths = Set['lemur', 'foo/bar', 'foo/baz', 'hoge/hoge']
|
55
55
|
@all_paths.each do |path|
|
56
56
|
@source.add_entry(path, path)
|
data/spec/rack_spec.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'hx'
|
4
|
+
require 'hx/rack'
|
5
|
+
require 'rack/mock'
|
6
|
+
|
7
|
+
describe Hx::Rack::Application do
|
8
|
+
before(:each) do
|
9
|
+
@input = FakeInput.new
|
10
|
+
@app = Hx::Rack::Application.new(@input, {})
|
11
|
+
@service = Rack::MockRequest.new(@app)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return 404 for unknown paths" do
|
15
|
+
response = @service.get("/bogus", :lint => true, :fatal => true)
|
16
|
+
response.status.to_i.should == 404
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return 200 with content for known paths" do
|
20
|
+
path = "existing"
|
21
|
+
content = "BLAH"
|
22
|
+
@input.add_entry(path, content)
|
23
|
+
response = @service.get("/#{path}", :lint => true, :fatal => true)
|
24
|
+
response.status.to_i.should == 200
|
25
|
+
response.body.to_s.should == content
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should infer content type from file extension by default" do
|
29
|
+
cases = { "html" => "text/html",
|
30
|
+
"jpeg" => "image/jpeg" }
|
31
|
+
for extension, mime_type in cases
|
32
|
+
path = "blah.#{extension}"
|
33
|
+
@input.add_entry(path, "")
|
34
|
+
response = @service.get("/#{path}", :lint => true, :fatal => true)
|
35
|
+
response.status.to_i.should == 200
|
36
|
+
response['Content-Type'].should == mime_type
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should allow entries to specify their mime type" do
|
41
|
+
path = "blah.html"
|
42
|
+
mime_type = "junk/garbage"
|
43
|
+
@input.add_entry(path, "", 'mime_type' => mime_type)
|
44
|
+
response = @service.get("/#{path}", :lint => true, :fatal => true)
|
45
|
+
response.status.to_i.should == 200
|
46
|
+
response['Content-Type'].should == mime_type
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should redirect for directories with indices" do
|
50
|
+
@input.add_entry("dir/index.html", "")
|
51
|
+
response = @service.get("/dir", :lint => true, :fatal => true)
|
52
|
+
response.status.to_i.should == 301
|
53
|
+
response['Location'].should == "/dir/"
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return index for directories" do
|
57
|
+
content = "SOME INDEX STUFF"
|
58
|
+
@input.add_entry("dir/index.html", content)
|
59
|
+
response = @service.get("/dir/", :lint => true, :fatal => true)
|
60
|
+
response.status.to_i.should == 200
|
61
|
+
response.body.to_s.should == content
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return index for the root" do
|
65
|
+
content = "ROOT INDEX YAY"
|
66
|
+
@input.add_entry("index.html", content)
|
67
|
+
response = @service.get("/", :lint => true, :fatal => true)
|
68
|
+
response.status.to_i.should == 200
|
69
|
+
response.body.to_s.should == content
|
70
|
+
end
|
71
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -5,26 +5,30 @@ require 'ostruct'
|
|
5
5
|
require 'spec'
|
6
6
|
require 'spec/autorun'
|
7
7
|
|
8
|
-
class
|
9
|
-
include Hx::
|
8
|
+
class FakeInput
|
9
|
+
include Hx::Filter
|
10
10
|
|
11
11
|
def initialize
|
12
12
|
@entries = {}
|
13
13
|
end
|
14
14
|
|
15
15
|
def add_entry(path, content, metadata={})
|
16
|
-
entry =
|
17
|
-
entry
|
16
|
+
entry = metadata.dup
|
17
|
+
entry['content'] = content
|
18
18
|
@entries[path] = entry
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
@entries.
|
21
|
+
def each_entry_path
|
22
|
+
@entries.each_key { |path| yield path }
|
23
23
|
self
|
24
24
|
end
|
25
25
|
|
26
26
|
def get_entry(path)
|
27
|
-
|
27
|
+
begin
|
28
|
+
@entries.fetch(path)
|
29
|
+
rescue IndexError
|
30
|
+
raise Hx::NoSuchEntryError, path
|
31
|
+
end
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- MenTaLguY
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-02-
|
12
|
+
date: 2010-02-16 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -47,18 +47,22 @@ files:
|
|
47
47
|
- lib/hx.rb
|
48
48
|
- lib/hx/backend/couchdb.rb
|
49
49
|
- lib/hx/backend/hobix.rb
|
50
|
+
- lib/hx/backend/rawfiles.rb
|
50
51
|
- lib/hx/commandline.rb
|
51
52
|
- lib/hx/listing/limit.rb
|
52
53
|
- lib/hx/listing/paginate.rb
|
53
54
|
- lib/hx/listing/recursiveindex.rb
|
54
55
|
- lib/hx/output/liquidtemplate.rb
|
56
|
+
- lib/hx/rack.rb
|
57
|
+
- lib/hx/rack/application.rb
|
55
58
|
- spec/cache_spec.rb
|
56
59
|
- spec/hx_dummy.rb
|
57
60
|
- spec/hx_dummy2.rb
|
58
|
-
- spec/
|
61
|
+
- spec/nullinput_spec.rb
|
59
62
|
- spec/overlay_spec.rb
|
60
63
|
- spec/pathfilter_spec.rb
|
61
64
|
- spec/pathops_spec.rb
|
65
|
+
- spec/rack_spec.rb
|
62
66
|
- spec/site_spec.rb
|
63
67
|
- spec/spec.opts
|
64
68
|
- spec/spec_helper.rb
|
@@ -95,8 +99,9 @@ test_files:
|
|
95
99
|
- spec/cache_spec.rb
|
96
100
|
- spec/hx_dummy.rb
|
97
101
|
- spec/pathfilter_spec.rb
|
98
|
-
- spec/
|
102
|
+
- spec/nullinput_spec.rb
|
99
103
|
- spec/site_spec.rb
|
100
104
|
- spec/hx_dummy2.rb
|
105
|
+
- spec/rack_spec.rb
|
101
106
|
- spec/overlay_spec.rb
|
102
107
|
- spec/pathops_spec.rb
|