hx 0.6.1 → 0.7.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/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
|