pbsimply 2.0.1 → 3.0.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/lib/pbsimply.rb CHANGED
@@ -1,11 +1,29 @@
1
1
  #!/bin/env ruby
2
+
2
3
  require 'yaml'
3
4
  require 'erb'
4
5
  require 'date'
6
+ require 'time'
7
+ require 'tmpdir'
5
8
  require 'fileutils'
6
9
  require 'optparse'
10
+ require 'digest/sha1'
11
+
12
+ # PureBuilder Simply Components
13
+ require 'pbsimply/docdb'
14
+ require 'pbsimply/docengine/base'
15
+ require 'pbsimply/prayer'
16
+ require 'pbsimply/plugger'
17
+ require 'pbsimply/hooks'
18
+ require 'pbsimply/frontmatter'
19
+ require 'pbsimply/accs'
7
20
 
8
21
  class PBSimply
22
+ include Prayer
23
+ include Plugger
24
+ include Frontmatter
25
+ include ACCS
26
+
9
27
  # Use Oj as JSON library for frontmatter passing if possible.
10
28
  begin
11
29
  require 'oj'
@@ -15,141 +33,6 @@ class PBSimply
15
33
  JSON_LIB = JSON
16
34
  end
17
35
 
18
- # ACCS namespace.
19
- module ACCS
20
- DEFINITIONS = {}
21
-
22
- # Built-in Accs index eRuby string.
23
- INDEX = <<'EOF'
24
- <%= YAML.dump(
25
- {
26
- "title" => @index["title"],
27
- "date" => @index["date"],
28
- "author" => @index["author"]
29
- }
30
- ) %>
31
- ---
32
-
33
- <%
34
- articles = Hash.new {|h,k| h[k] = Array.new }
35
-
36
- if @config["accs_across_category"]
37
- @indexes.each {|filename, index| articles["default"].push index }
38
- else
39
- @indexes.each {|filename, index| articles[(index["category"] || "default")].push index }
40
- end
41
-
42
- %>
43
-
44
- % articles.keys.sort.each do |catname|
45
- % cat = articles[catname]
46
-
47
- % unless articles.length == 1
48
- # <%= catname %>
49
- % end
50
-
51
- <%
52
- sort_method = case @config["accs_sort_by"]
53
- when "title"
54
- lambda {|i| [i["title"].to_s, i["date"]] }
55
- when "name"
56
- lambda {|i| [i["_filename"].to_s, i["title"].to_s, i["date"]] }
57
- when "serial"
58
- lambda {|i| [i["serial"].to_s, i["date"], i["_filename"].to_s] }
59
- else
60
- lambda {|i| [i["date"], i["title"].to_s, i["_last_update"].to_i] }
61
- end
62
-
63
- list = if @config["accs_order"] == "desc"
64
- cat.sort_by(&sort_method).reverse
65
- else
66
- cat.sort_by(&sort_method)
67
- end
68
-
69
- list.each do |i|
70
- %>* [<%= i["title"] %>](<%= i["page_url"] %>)
71
- <% end %>
72
-
73
- % end
74
- EOF
75
- end
76
-
77
- # Abstruct super class.
78
- class DocDB
79
- def dump(object)
80
- File.open(File.join(@dir, ".indexes.#{@ext}"), "w") do |f|
81
- f.write @store_class.dump(object)
82
- end
83
- end
84
-
85
- def load
86
- File.open(File.join(@dir, ".indexes.#{@ext}"), "r") do |f|
87
- next @store_class.load(f)
88
- end
89
- end
90
-
91
- def exist?
92
- File.exist?(File.join(@dir, ".indexes.#{@ext}"))
93
- end
94
-
95
- def path
96
- File.join(@dir, ".indexes.#{@ext}")
97
- end
98
-
99
- def cmp_obj(frontmatter)
100
- @store_class.load(@store_class.dump(frontmatter))
101
- end
102
-
103
- class Marshal < DocDB
104
- def initialize(dir)
105
- @dir = dir
106
- @store_class = ::Marshal
107
- @ext = "rbm"
108
- end
109
-
110
- def cmp_obj(frontmatter)
111
- frontmatter.dup
112
- end
113
- end
114
-
115
- class JSON < DocDB
116
- def initialize(dir)
117
- @dir = dir
118
- @store_class = ::JSON
119
- @ext = "json"
120
- end
121
- end
122
-
123
- class Oj < DocDB::JSON
124
- def initialize(dir)
125
- require 'oj'
126
- @dir = dir
127
- @ext = "json"
128
- @store_class = ::Oj
129
- end
130
- end
131
-
132
- class YAML < DocDB
133
- def initialize(dir)
134
- @dir = dir
135
- @store_class = ::YAML
136
- @ext = "yaml"
137
- end
138
- end
139
- end
140
-
141
-
142
- POST_PROCESSORS = {
143
- ".rb" => "ruby",
144
- ".pl" => "perl",
145
- ".py" => "python",
146
- ".lua" => "lua",
147
- ".bash" => "bash",
148
- ".zsh" => "zsh",
149
- ".php" => "php",
150
- ".sed" => ["sed", ->(script, target) { ["-f", script, target] } ]
151
- }
152
-
153
36
  ###############################################
154
37
  # SETUP FUNCTIONS #
155
38
  ###############################################
@@ -183,8 +66,6 @@ EOF
183
66
  outdir = [@config["outdir"], @dir].join("/")
184
67
  end
185
68
 
186
- p dir
187
-
188
69
  # Format for Indexes database
189
70
  @db = case @config["dbstyle"]
190
71
  when "yaml"
@@ -225,6 +106,7 @@ EOF
225
106
  @accs = nil
226
107
  @accs_index = {}
227
108
  @now = Time.now
109
+ @hooks = PBSimply::Hooks.new(self)
228
110
  end
229
111
 
230
112
  # Process command-line
@@ -234,6 +116,7 @@ EOF
234
116
  opts.on("-f", "--force-refresh") { @refresh = true }
235
117
  opts.on("-X", "--ignore-ext") { @ignore_ext = true }
236
118
  opts.on("-I", "--skip-index") { @skip_index = true }
119
+ opts.on("-A", "--skip-accs") { @skip_accs = true }
237
120
  opts.on("-o FILE", "--output") {|v| @outfile = v }
238
121
  opts.on("-m FILE", "--additional-metafile") {|v| @add_meta = Psych.unsafe_load(File.read(v))}
239
122
  opts.parse!(ARGV)
@@ -256,6 +139,7 @@ EOF
256
139
  @indexes = Hash.new
257
140
  end
258
141
  @docobject[:indexes] = @indexes
142
+ ENV["pbsimply_indexes"] = @db.path
259
143
  end
260
144
 
261
145
  def target_file_extensions
@@ -269,7 +153,6 @@ EOF
269
153
 
270
154
  attr :indexes
271
155
 
272
-
273
156
  ###############################################
274
157
  # PROCESSING FUNCTIONS #
275
158
  ###############################################
@@ -284,12 +167,15 @@ EOF
284
167
 
285
168
  STDERR.puts "Checking Frontmatter..."
286
169
  Dir.foreach(@dir) do |filename|
287
- next if filename == "." || filename == ".."
170
+ next if filename == "." || filename == ".." || filename == ".index.md"
288
171
  if filename =~ /^\./ || filename =~ /^draft-/
289
- draft_articles.push filename.sub(/^(?:\.|draft-)/, "")
172
+ draft_articles.push({
173
+ article_filename: filename.sub(/^(?:\.|draft-)/, ""),
174
+ filename: filename,
175
+ source_file_path: File.join(@dir, filename)
176
+ })
290
177
  next
291
178
  end
292
- next unless File.file?([@dir, filename].join("/"))
293
179
 
294
180
  if !@ignore_ext and not target_file_extensions.include? File.extname filename
295
181
  next
@@ -301,8 +187,11 @@ EOF
301
187
  frontmatter.merge!(@add_meta) if @add_meta
302
188
 
303
189
  if frontmatter["draft"]
304
- @indexes.delete(filename) if @indexes[filename]
305
- draft_articles.push filename
190
+ draft_articles.push({
191
+ article_filename: filename,
192
+ filename: filename,
193
+ source_file_path: File.join(@dir, filename)
194
+ })
306
195
  next
307
196
  end
308
197
 
@@ -313,33 +202,35 @@ EOF
313
202
  target_docs.push([filename, frontmatter, pos])
314
203
  end
315
204
 
316
- # Delete turn to draft article.
317
- draft_articles.each do |df|
318
- STDERR.puts "#{df} was turn to draft. deleting..."
319
- [df, (df + ".html"), File.basename(df, ".*"), (File.basename(df, ".*") + ".html")].each do |tfn|
320
- tfp = File.join(@config["outdir"], @dir, tfn)
321
- File.delete tfp if File.file?(tfp)
322
- end
323
- end
205
+ delete_turn_draft draft_articles
206
+
207
+ proc_docs target_docs
208
+
209
+ delete_missing
324
210
 
325
211
  # Save index.
326
212
  @db.dump(@indexes) unless @skip_index
327
213
 
328
- STDERR.puts "Blessing..."
329
-
330
- # Modify frontmatter `BLESSING`
331
- target_docs.each do |filename, frontmatter, pos|
332
- if @config["bless_style"] == "cmd"
333
- bless_cmd(frontmatter)
334
- else
335
- bless_ruby(frontmatter)
336
- end
214
+ # ACCS processing
215
+ if @accs && !target_docs.empty?
216
+ process_accs
337
217
  end
218
+ end
338
219
 
339
- STDERR.puts "Checking modification..."
220
+ def proc_docs target_docs
221
+ # Exclude unchanged documents.
222
+ if @indexes && @indexes_orig
223
+ STDERR.puts "Checking modification..."
224
+ target_docs.delete_if {|filename, frontmatter, pos| !check_modify([@dir, filename], frontmatter)}
225
+ end
340
226
 
341
- target_docs.delete_if {|filename, frontmatter, pos| !check_modify([@dir, filename], frontmatter)}
227
+ # Modify frontmatter `BLESSING'
228
+ target_docs.each do |filename, frontmatter, pos|
229
+ STDERR.puts "Blessing #{filename}..."
230
+ bless frontmatter
231
+ end
342
232
 
233
+ # Ready.
343
234
  STDERR.puts "Okay, Now ready. Process documents..."
344
235
 
345
236
  # Proccess documents
@@ -348,135 +239,107 @@ EOF
348
239
  @index = frontmatter
349
240
  File.open(File.join(@dir, filename)) do |f|
350
241
  f.seek(pos)
351
- File.open(".current_document#{ext}", "w") {|fo| fo.write f.read}
242
+ File.open(File.join(@workdir, "current_document#{ext}"), "w") {|fo| fo.write f.read}
352
243
  end
353
244
 
354
245
  STDERR.puts "Processing #{filename}"
355
246
  generate(@dir, filename, frontmatter)
356
247
  end
357
-
358
- @db.dump(@indexes) unless @skip_index
359
-
248
+
249
+ # Call post plugins
360
250
  post_plugins
361
251
 
362
- # ACCS processing
363
- if @accs && !target_docs.empty?
364
- process_accs
365
- end
252
+ # Call hooks
253
+ @hooks.post.run({this_time_processed: @this_time_processed})
366
254
  end
367
255
 
368
- # Run PureBuilder Simply.
369
- def main
370
- # If target file is regular file, run as single mode.
371
- @singlemode = true if File.file?(@dir)
372
-
373
- # Check single file mode.
374
- if @singlemode
375
- # Single file mode
376
- if @dir =~ %r:(.*)/([^/]+):
377
- dir = $1
378
- filename = $2
379
- else
380
- dir = "."
381
- filename = @dir
256
+ # Delete turn to draft article.
257
+ def delete_turn_draft draft_articles
258
+ STDERR.puts "Checking turn to draft..."
259
+ draft_articles.each do |dah|
260
+ df = dah[:article_filename]
261
+ [df, (df + ".html"), File.basename(df, ".*"), (File.basename(df, ".*") + ".html")].each do |tfn|
262
+ tfp = File.join(@config["outdir"], @dir, tfn)
263
+ if File.file?(tfp)
264
+ STDERR.puts "#{df} was turn to draft."
265
+ @hooks.delete.run({target_file_path: tfp, source_file_path: dah[:source_file_path]})
266
+ File.delete tfp if @config["auto_delete"]
267
+ end
382
268
  end
383
- @dir = dir
384
- setup_config(dir)
269
+ @indexes.delete df if @indexes[df]
270
+ end
271
+ end
385
272
 
386
- load_index
273
+ # Delete missing source
274
+ def delete_missing
275
+ return unless @indexes
276
+ STDERR.puts "Checking missing article..."
277
+ missing_keys = []
278
+ @indexes.each do |k, v|
279
+ next if !v["source_path"] || !v["dest_path"]
280
+ unless File.exist? v["source_path"]
281
+ STDERR.puts "#{k} is missing."
282
+ missing_keys.push k
283
+ @hooks.delete.run({target_file_path: v["dest_path"] ,source_file_path: v["source_path"]})
284
+ File.delete v["dest_path"] if @config["auto_delete"]
285
+ end
286
+ end
287
+ missing_keys.each {|k| @indexes.delete k }
288
+ end
387
289
 
388
- frontmatter, pos = read_frontmatter(dir, filename)
389
- frontmatter = @frontmatter.merge frontmatter
390
- @index = frontmatter
290
+ # Run PureBuilder Simply.
291
+ def main
292
+ @hooks.load
293
+ Dir.mktmpdir("pbsimply") do |dir|
294
+ ENV["pbsimply_working_dir"] = dir
295
+ @workdir ||= dir
296
+ @workfile_frontmatter ||= File.join(@workdir, "pbsimply-frontmatter.json")
297
+ ENV["pbsimply_frontmatter"] = @workfile_frontmatter
298
+ @workfile_pandoc_defaultfiles ||= File.join(@workdir, "pbsimply-defaultfiles.yaml")
299
+ # If target file is regular file, run as single mode.
300
+ @singlemode = true if File.file?(@dir)
301
+
302
+ # Check single file mode.
303
+ if @singlemode
304
+ # Single file mode
305
+ if @dir =~ %r:(.*)/([^/]+):
306
+ dir = $1
307
+ filename = $2
308
+ else
309
+ dir = "."
310
+ filename = @dir
311
+ end
312
+ @dir = dir
313
+ setup_config(dir)
391
314
 
392
- ext = File.extname filename
393
- File.open(File.join(dir, filename)) do |f|
394
- f.seek(pos)
395
- File.open(".current_document#{ext}", "w") {|fo| fo.write f.read}
396
- end
315
+ load_index
397
316
 
398
- generate(dir, filename, frontmatter)
317
+ frontmatter, pos = read_frontmatter(dir, filename)
318
+ frontmatter = @frontmatter.merge frontmatter
319
+ @index = frontmatter
399
320
 
400
- post_plugins(frontmatter)
321
+ proc_docs([[filename, frontmatter, pos]])
401
322
 
402
- else
403
- # Normal (directory) mode.
404
- setup_config(@dir)
405
- load_index
323
+ if File.exist?(File.join(@dir, ".accs.yaml")) && !@accs_processing && !@skip_accs
324
+ single_accs filename, frontmatter
325
+ end
326
+ else
327
+ # Normal (directory) mode.
328
+ setup_config(@dir)
329
+ load_index
406
330
 
407
- @accs = true if File.exist?(File.join(@dir, ".accs.yaml"))
331
+ @accs = true if File.exist?(File.join(@dir, ".accs.yaml"))
408
332
 
409
- # Check existing in indexes.
410
- @indexes.delete_if {|k,v| ! File.exist?([@dir, k].join("/")) }
333
+ # Check existing in indexes.
334
+ @indexes.delete_if {|k,v| ! File.exist?([@dir, k].join("/")) }
411
335
 
412
- proc_dir
413
- end
414
- ensure
415
- # Clean up temporary files.
416
- Dir.children(".").each do |fn|
417
- if fn[0, 22] == ".pbsimply-defaultfiles.yaml" or
418
- fn[0, 21] == ".pbsimply-frontmatter" or
419
- fn[0, 17] == ".current_document"
420
- File.delete fn
421
- end
422
- end
423
- end
424
-
425
- def pre_plugins(procdoc, frontmatter)
426
- if File.directory?(".pre_generate")
427
- STDERR.puts("Processing with pre plugins")
428
- script_file = File.join(".pre_generate", script_file)
429
- Dir.entries(".pre_generate").sort.each do |script_file|
430
- next if script_file =~ /^\./
431
- STDERR.puts "Running script: #{File.basename script_file}"
432
- pre_script_result = nil
433
- script_cmdline = case
434
- when File.executable?(script_file)
435
- [script_file, procdoc]
436
- when POST_PROCESSORS[File.extname(script_file)]
437
- [POST_PROCESSORS[File.extname(script_file)], script_file, procdoc]
438
- else
439
- ["perl", script_file, procdoc]
440
- end
441
- IO.popen({"pbsimply_doc_frontmatter" => YAML.dump(frontmatter)}, script_cmdline) do |io|
442
- pre_script_result = io.read
443
- end
444
- File.open(procdoc, "w") {|f| f.write pre_script_result}
445
- end
446
- end
447
- end
448
-
449
- def post_plugins(frontmatter=nil)
450
- if File.directory?(".post_generate")
451
-
452
- STDERR.puts("Processing with post plugins")
453
-
454
- @this_time_processed.each do |v|
455
- STDERR.puts "Processing #{v[:dest]} (from #{v[:source]})"
456
- procdoc = v[:dest]
457
- frontmatter ||= @indexes[File.basename v[:source]]
458
- File.open(".pbsimply-frontmatter.json", "w") {|f| f.write JSON_LIB.dump(frontmatter)}
459
- Dir.entries(".post_generate").sort.each do |script_file|
460
- next if script_file =~ /^\./
461
- STDERR.puts "Running script: #{script_file}"
462
- script_file = File.join(".post_generate", script_file)
463
- post_script_result = nil
464
- script_cmdline = case
465
- when File.executable?(script_file)
466
- [script_file, procdoc]
467
- when POST_PROCESSORS[File.extname(script_file)]
468
- [POST_PROCESSORS[File.extname(script_file)], script_file, procdoc]
469
- else
470
- ["perl", script_file, procdoc]
471
- end
472
- IO.popen({"pbsimply_frontmatter" => ".pbsimply-frontmatter.json", "pbsimply_indexes" => @db.path}, script_cmdline) do |io|
473
- post_script_result = io.read
474
- end
475
-
476
- File.open(procdoc, "w") {|f| f.write post_script_result}
477
- end
336
+ proc_dir
478
337
  end
479
338
  end
339
+ ensure
340
+ @workdir = nil
341
+ @workfile_frontmatter = nil
342
+ @workfile_pandoc_defaultfiles = nil
480
343
  end
481
344
 
482
345
  def generate(dir, filename, frontmatter)
@@ -485,8 +348,10 @@ EOF
485
348
  # Preparing and pre script.
486
349
  orig_filepath = [dir, filename].join("/")
487
350
  ext = File.extname(filename)
488
- procdoc = sprintf(".current_document%s", ext)
351
+ procdoc = File.join(@workdir, "current_document#{ext}")
352
+
489
353
  pre_plugins(procdoc, frontmatter)
354
+ @hooks.pre.run({procdoc: procdoc, frontmatter: frontmatter})
490
355
 
491
356
  doc = process_document(dir, filename, frontmatter, orig_filepath, ext, procdoc) # at sub-class
492
357
 
@@ -497,54 +362,21 @@ EOF
497
362
  end
498
363
 
499
364
  # Write out
500
- outext = frontmatter["force_ext"] || ".html"
501
- outpath = case
502
- when @outfile
503
- @outfile
504
- when @accs_processing
505
- File.join(@config["outdir"], @dir, "index") + outext
506
- else
507
- File.join(@config["outdir"], @dir, File.basename(filename, ".*")) + outext
508
- end
365
+ outpath = frontmatter["dest_path"]
509
366
 
510
367
  File.open(outpath, "w") do |f|
511
368
  f.write(doc)
512
369
  end
513
370
 
514
- # Mark processed
515
- @this_time_processed.push({source: orig_filepath, dest: outpath})
516
- end
517
-
518
- # letsaccs
519
- #
520
- # This method called on the assumption that processed all documents and run as directory mode.
521
- def process_accs
522
- STDERR.puts "Processing ACCS index..."
523
- if File.exist?(File.join(@dir, ".accsindex.erb"))
524
- erbtemplate = File.read(File.join(@dir, ".accsindex.erb"))
525
- elsif File.exist?(".accsindex.erb")
526
- erbtemplate = File.read(".accsindex.erb")
527
- else
528
- erbtemplate = ACCS::INDEX
529
- end
530
-
531
- # Get infomation
532
- @accs_index = Psych.unsafe_load(File.read([@dir, ".accs.yaml"].join("/")))
533
-
534
- @accs_index["title"] ||= (@config["accs_index_title"] || "Index")
535
- @accs_index["date"] ||= Time.now.strftime("%Y-%m-%d")
536
- @accs_index["pagetype"] = "accs_index"
537
-
538
- @index = @frontmatter.merge @accs_index
539
-
540
- doc = ERB.new(erbtemplate, trim_mode: "%<>").result(binding)
541
- File.open(File.join(@dir, ".index.md"), "w") do |f|
542
- f.write doc
543
- end
371
+ # Hooks for processed document.
372
+ @hooks.process.run({
373
+ outpath: outpath,
374
+ frontmatter: frontmatter,
375
+ procdoc: procdoc
376
+ })
544
377
 
545
- accsmode
546
- @dir = File.join(@dir, ".index.md")
547
- main
378
+ # Mark processed
379
+ @this_time_processed.push({source: orig_filepath, dest: outpath, frontmatter: frontmatter})
548
380
  end
549
381
 
550
382
  ###############################################
@@ -553,198 +385,21 @@ EOF
553
385
 
554
386
  private
555
387
 
556
- # Turn on ACCS processing mode.
557
- def accsmode
558
- @accs_processing = true
559
- @singlemode = true
560
- @skip_index = true
561
- end
562
-
563
- # Read Frontmatter from the document.
564
- # This method returns frontmatter, pos.
565
- # pos means position at end of Frontmatter on the file.
566
- def read_frontmatter(dir, filename)
567
- frontmatter = nil
568
- pos = nil
569
-
570
- if File.exist? File.join(dir, ".meta." + filename)
571
- # Load standalone metadata YAML.
572
- frontmatter = Psych.unsafe_load(File.read(File.join(dir, (".meta." + filename))))
573
- pos = 0
574
- else
575
-
576
- case File.extname filename
577
- when ".md"
578
-
579
- # Load Markdown's YAML frontmatter.
580
- File.open(File.join(dir, filename)) do |f|
581
- l = f.gets
582
- next unless l && l.chomp == "---"
583
-
584
- lines = []
585
-
586
- while l = f.gets
587
- break if l.nil?
588
-
589
- break if l.chomp == "---"
590
- lines.push l
591
- end
592
-
593
- next if f.eof?
594
-
595
- begin
596
- frontmatter = Psych.unsafe_load(lines.join)
597
- rescue => e
598
- STDERR.puts "!CRITICAL: Cannot parse frontmatter."
599
- raise e
600
- end
601
-
602
- pos = f.pos
603
- end
604
-
605
- when ".rst"
606
- # ReSTRUCTURED Text
607
-
608
- File.open(File.join(dir, filename)) do |f|
609
- l = f.gets
610
- if l =~ /:([A-Za-z_-]+): (.*)/ #docinfo
611
- frontmatter = { $1 => [$2.chomp] }
612
- last_key = $1
613
-
614
- # Read docinfo
615
- while(l = f.gets)
616
- break if l =~ /^\s*$/ # End of docinfo
617
- if l =~ /^\s+/ # Continuous line
618
- docinfo_lines.last.push($'.chomp)
619
- elsif l =~ /:([A-Za-z_-]+): (.*)/
620
- frontmatter[$1] = [$2.chomp]
621
- last_key = $1
622
- end
623
- end
624
-
625
- # Treat docinfo lines
626
- frontmatter.each do |k,v|
627
- v = v.join(" ")
628
- #if((k == "author" || k == "authors") && v.include?(";")) # Multiple authors.
629
- if(v.include?(";")) # Multiple element.
630
- v = v.split(/\s*;\s*/)
631
-
632
- elsif k == "date" # Date?
633
- # Datetime?
634
- if v =~ /[0-2][0-9]:[0-6][0-9]/
635
- v = DateTime.parse(v)
636
- else
637
- v = Date.parse(v)
638
- end
639
- elsif v == "yes" || v == "true"
640
- v = true
641
- else # Simple String.
642
- nil # keep v
643
- end
644
-
645
- frontmatter[k] = v
646
- end
647
-
648
- elsif l && l.chomp == ".." #YAML
649
- # Load ReST YAML that document begins comment and block is yaml.
650
- lines = []
651
-
652
- while(l = f.gets)
653
- if(l !~ /^\s*$/ .. l =~ /^\s*$/)
654
- if l=~ /^\s*$/
655
- break
656
- else
657
- lines.push l
658
- end
659
- end
660
- end
661
- next if f.eof?
662
-
663
-
664
- # Rescue for failed to read YAML.
665
- begin
666
- frontmatter = Psych.unsafe_load(lines.map {|i| i.sub(/^\s*/, "") }.join)
667
- rescue
668
- STDERR.puts "Error in parsing ReST YAML frontmatter (#{$!})"
669
- next
670
- end
671
- else
672
- next
673
- end
674
-
675
- pos = f.pos
676
-
677
- end
678
- end
679
- end
680
-
681
- abort "This document has no frontmatter" unless frontmatter
682
- abort "This document has no title." unless frontmatter["title"]
683
-
684
-
685
- ### Additional meta values. ###
686
- frontmatter["source_directory"] = dir # Source Directory
687
- frontmatter["source_filename"] = filename # Source Filename
688
- frontmatter["source_path"] = File.join(dir, filename) # Source Path
689
- # URL in site.
690
- this_url = (File.join(dir, filename)).sub(/^[\.\/]*/) { @config["self_url_prefix"] || "/" }.sub(/\.[a-zA-Z0-9]+$/, ".html")
691
- frontmatter["page_url"] = this_url
692
- # URL in site with URI encode.
693
- frontmatter["page_url_encoded"] = ERB::Util.url_encode(this_url)
694
- frontmatter["page_url_encoded_external"] = ERB::Util.url_encode((File.join(dir, filename)).sub(/^[\.\/]*/) { @config["self_url_external_prefix"] || "/" }.sub(/\.[a-zA-Z0-9]+$/, ".html"))
695
- frontmatter["page_html_escaped"] = ERB::Util.html_escape(this_url)
696
- frontmatter["page_html_escaped_external"] = ERB::Util.html_escape((File.join(dir, filename)).sub(/^[\.\/]*/) { @config["self_url_external_prefix"] || "/" }.sub(/\.[a-zA-Z0-9]+$/, ".html"))
697
- # Title with URL Encoded.
698
- frontmatter["title_encoded"] = ERB::Util.url_encode(frontmatter["title"])
699
- frontmatter["title_html_escaped"] = ERB::Util.html_escape(frontmatter["title"])
700
- fts = frontmatter["timestamp"]
701
- fts = fts.to_datetime if Time === fts
702
- if DateTime === fts
703
- frontmatter["timestamp_xmlschema"] = fts.xmlschema
704
- frontmatter["timestamp_jplocal"] = fts.strftime('%Y年%m月%d日 %H時%M分%S秒')
705
- frontmatter["timestamp_rubytimestr"] = fts.strftime('%a %b %d %H:%M:%S %Z %Y')
706
- frontmatter["timestamp_str"] = fts.strftime("%Y-%m-%d %H:%M:%S %Z")
707
- elsif Date === fts
708
- frontmatter["timestamp_xmlschema"] = fts.xmlschema
709
- frontmatter["timestamp_jplocal"] = fts.strftime('%Y年%m月%d日')
710
- frontmatter["timestamp_rubytimestr"] = fts.strftime('%a %b %d')
711
- frontmatter["timestamp_str"] = fts.strftime("%Y-%m-%d")
712
- elsif Date === frontmatter["Date"]
713
- fts = frontmatter["Date"]
714
- frontmatter["timestamp_xmlschema"] = fts.xmlschema
715
- frontmatter["timestamp_jplocal"] = fts.strftime('%Y年%m月%d日')
716
- frontmatter["timestamp_rubytimestr"] = fts.strftime('%a %b %d')
717
- frontmatter["timestamp_str"] = fts.strftime("%Y-%m-%d")
718
- end
719
-
720
- fsize = FileTest.size(File.join(dir, filename))
721
- mtime = File.mtime(File.join(dir, filename)).to_i
722
-
723
- frontmatter["_filename"] ||= filename
724
- frontmatter["pagetype"] ||= "post"
725
-
726
- frontmatter["_size"] = fsize
727
- frontmatter["_mtime"] = mtime
728
- frontmatter["_last_proced"] = @now.to_i
729
-
730
- if File.extname(filename) == ".md"
731
- frontmatter["_docformat"] = "Markdown"
732
- elsif File.extname(filename) == ".rst" || File.extname(filename) == ".rest"
733
- frontmatter["_docformat"] = "ReST"
734
- end
735
-
736
- frontmatter["date"] ||= @now.strftime("%Y-%m-%d %H:%M:%S")
737
-
738
- return frontmatter, pos
739
- end
740
-
741
388
  # Check is the article modified? (or force update?)
742
389
  def check_modify(path, frontmatter)
743
390
  modify = true
744
391
  index = @indexes_orig[path[1]].dup || {}
745
392
  frontmatter = @db.cmp_obj(frontmatter)
393
+
394
+ # Remove unstable metadata for compartion.
746
395
  index.delete("_last_proced")
396
+ index.delete("source_path")
397
+ index.delete("source_directory")
398
+ index.delete("dest_path")
747
399
  frontmatter.delete("_last_proced")
400
+ frontmatter.delete("source_path")
401
+ frontmatter.delete("source_directory")
402
+ frontmatter.delete("dest_path")
748
403
 
749
404
  if index == frontmatter
750
405
  STDERR.puts "#{path[1]} is not modified."
@@ -761,311 +416,4 @@ EOF
761
416
  modify
762
417
  end
763
418
  end
764
-
765
- def bless_ruby(frontmatter)
766
- # BLESSING (Always)
767
- if PureBuilder.const_defined?(:BLESS) && Proc === PureBuilder::BLESS
768
- begin
769
- PureBuilder::BLESS.(frontmatter, self)
770
- rescue
771
- STDERR.puts "*** BLESSING PROC ERROR ***"
772
- raise
773
- end
774
- end
775
-
776
- # BLESSING (ACCS)
777
- if @accs && PureBuilder::ACCS.const_defined?(:BLESS) && Proc === PureBuilder::ACCS::BLESS
778
- begin
779
- PureBuilder::ACCS::BLESS.(frontmatter, self)
780
- rescue
781
- STDERR.puts "*** ACCS BLESSING PROC ERROR ***"
782
- raise
783
- end
784
- end
785
-
786
- # ACCS DEFINITIONS
787
- if @accs
788
- if Proc === PureBuilder::ACCS::DEFINITIONS[:next]
789
- i = PureBuilder::ACCS::DEFINITIONS[:next].call(frontmatter, self)
790
- frontmatter["next_article"] = i if i
791
- end
792
- if Proc === PureBuilder::ACCS::DEFINITIONS[:prev]
793
- i = PureBuilder::ACCS::DEFINITIONS[:prev].call(frontmatter, self)
794
- frontmatter["prev_article"] = i if i
795
- end
796
- end
797
-
798
- autobless(frontmatter)
799
- end
800
-
801
- def bless_cmd(frontmatter)
802
- File.open(".pbsimply-frontmatter.json", "w") {|f| f.write JSON_LIB.dump(frontmatter) }
803
- # BLESSING (Always)
804
- if @config["bless_cmd"]
805
- (Array === @config["bless_cmd"] ? system(*@config["bless_cmd"]) : system(@config["bless_cmd"]) ) or abort "*** BLESS COMMAND RETURNS NON-ZERO STATUS"
806
- end
807
- # BLESSING (ACCS)
808
- if @config["bless_accscmd"]
809
- (Array === @config["bless_accscmd"] ? system({"pbsimply_frontmatter" => ".pbsimply-frontmatter.json", "pbsimply_indexes" => @db.path}, *@config["bless_accscmd"]) : system({"pbsimply_frontmatter" => ".pbsimply-frontmatter.json", "pbsimply_indexes" => @db.path}, @config["bless_accscmd"]) ) or abort "*** BLESS COMMAND RETURNS NON-ZERO STATUS"
810
- end
811
- mod_frontmatter = JSON.load(File.read(".pbsimply-frontmatter.json"))
812
- frontmatter.replace(mod_frontmatter)
813
-
814
- autobless(frontmatter)
815
- end
816
-
817
- # Blessing automatic method with configuration.
818
- def autobless(frontmatter)
819
- catch(:accs_rel) do
820
- # find Next/Prev page on accs
821
- if @accs && @config["blessmethod_accs_rel"]
822
- # Preparing. Run at once.
823
- if !@article_order
824
- @rev_article_order_index = {}
825
-
826
- case @config["blessmethod_accs_rel"]
827
- when "numbering"
828
- @article_order = @indexes.to_a.sort_by {|i| i[1]["_filename"].to_i }
829
- when "date"
830
- begin
831
- @article_order = @indexes.to_a.sort_by {|i| i[1]["date"]}
832
- rescue
833
- abort "*** Automatic Blessing Method Error: Maybe some article have no date."
834
- end
835
- when "timestamp"
836
- begin
837
- @article_order = @indexes.to_a.sort_by {|i| i[1]["timestamp"]}
838
- rescue
839
- abort "*** Automatic Blessing Method Error: Maybe some article have no timetsamp."
840
- end
841
- when "lexical"
842
- @article_order = @indexes.to_a.sort_by {|i| i[1]["_filename"]}
843
- end
844
- @article_order.each_with_index {|x,i| @rev_article_order_index[x[0]] = i }
845
- end
846
-
847
- throw(:accs_rel) unless index = @rev_article_order_index[frontmatter["_filename"]]
848
- if @article_order[index + 1]
849
- frontmatter["next_article"] = {"url" => @article_order[index + 1][1]["page_url"],
850
- "title" => @article_order[index + 1][1]["title"]}
851
- end
852
- if index > 0
853
- frontmatter["prev_article"] = {"url" => @article_order[index - 1][1]["page_url"],
854
- "title" => @article_order[index - 1][1]["title"]}
855
- end
856
- end
857
- end
858
- end
859
-
860
- ###############################################
861
- # DOCUMENT PROCESSORS #
862
- ###############################################
863
-
864
-
865
- module Processor
866
-
867
- # Pandoc processor
868
- class Pandoc < PBSimply
869
- def initialize(config)
870
- @pandoc_default_file = {}
871
-
872
- # -d
873
- @pandoc_default_file = {
874
- "to" => "html5",
875
- "standalone" => true
876
- }
877
- super
878
- end
879
-
880
- def setup_config(dir)
881
- super
882
- @pandoc_default_file["template"] = @config["template"]
883
-
884
- if @config["css"]
885
- if @config["css"].kind_of?(String)
886
- @pandoc_default_file["css"] = [@config["css"]]
887
- elsif @config["css"].kind_of?(Array)
888
- @pandoc_default_file["css"] = @config["css"]
889
- else
890
- abort "css in Config should be a String or an Array."
891
- end
892
- end
893
-
894
- if @config["toc"]
895
- @pandoc_default_file["toc"] = true
896
- end
897
-
898
- if Hash === @config["pandoc_additional_options"]
899
- @pandoc_default_file.merge! @config["pandoc_additional_options"]
900
- end
901
-
902
- end
903
-
904
- # Invoke pandoc, parse and format and write out.
905
- def print_fileproc_msg(filename)
906
- STDERR.puts "#{filename} is going Pandoc."
907
- end
908
-
909
- def process_document(dir, filename, frontmatter, orig_filepath, ext, procdoc)
910
- doc = nil
911
-
912
- File.open(".pbsimply-defaultfiles.yaml", "w") {|f| YAML.dump(@pandoc_default_file, f)}
913
- File.open(".pbsimply-frontmatter.yaml", "w") {|f| YAML.dump(frontmatter, f)}
914
-
915
- # Go Pandoc
916
- pandoc_cmdline = ["pandoc"]
917
- pandoc_cmdline += ["-d", ".pbsimply-defaultfiles.yaml", "--metadata-file", ".pbsimply-frontmatter.yaml", "-M", "title:#{frontmatter["title"]}"]
918
- pandoc_cmdline += ["-f", frontmatter["input_format"]] if frontmatter["input_format"]
919
- pandoc_cmdline += [ procdoc ]
920
- IO.popen((pandoc_cmdline)) do |io|
921
- doc = io.read
922
- end
923
-
924
- # Abort if pandoc returns non-zero status
925
- if $?.exitstatus != 0
926
- abort "Pandoc returns exit code #{$?.exitstatus}"
927
- end
928
-
929
- doc
930
- end
931
-
932
- def target_file_extensions
933
- [".md", ".rst"]
934
- end
935
- end
936
-
937
- # RDoc family Base
938
- class PbsRBase < PBSimply
939
- def initialize(config)
940
- require 'rdoc'
941
- require 'rdoc/markup/to_html'
942
-
943
- @rdoc_options = RDoc::Options.new
944
- @rdoc_markup = RDoc::Markup.new
945
-
946
- super
947
- end
948
-
949
- def process_document(dir, filename, frontmatter, orig_filepath, ext, procdoc)
950
- # Getting HTML string.
951
- rdoc = RDoc::Markup::ToHtml.new(@rdoc_options, @rdoc_markup)
952
- article_body = rdoc.convert(get_markup_document(procdoc))
953
-
954
- # Process with eRuby temaplte.
955
- erb_template = ERB.new(File.read(@config["template"]), trim_mode: '%<>')
956
- doc = erb_template.result(binding)
957
-
958
- doc
959
- end
960
- end
961
-
962
- # RDoc/Markdown processor
963
- class PbsRMakrdown < PbsRBase
964
- def initialize(config)
965
- require 'rdoc'
966
- require 'rdoc/markdown'
967
- super
968
- end
969
-
970
- def print_fileproc_msg(filename)
971
- STDERR.puts "#{filename} generate with RDoc/Markdown"
972
- end
973
-
974
- def get_markup_document procdoc
975
- RDoc::Markdown.parse(File.read procdoc)
976
- end
977
- end
978
-
979
- # RDoc processor
980
- class PbsRDoc < PbsRBase
981
- def print_fileproc_msg(filename)
982
- STDERR.puts "#{filename} generate with RDoc"
983
- end
984
-
985
- def get_markup_document procdoc
986
- File.read procdoc
987
- end
988
-
989
- def target_file_extensions
990
- [".rdoc"]
991
- end
992
- end
993
-
994
- class PbsRedCarpet < PBSimply
995
- def initialize(config)
996
- require 'redcarpet'
997
- super
998
- end
999
-
1000
- def setup_config(dir)
1001
- super
1002
- @rc_extension = @config["redcarpet_extensions"] || {}
1003
- end
1004
-
1005
- def print_fileproc_msg(filename)
1006
- STDERR.puts "#{filename} generate with Redcarpet Markdown"
1007
- end
1008
-
1009
- def process_document(dir, filename, frontmatter, orig_filepath, ext, procdoc)
1010
- # Getting HTML string.
1011
- markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, **@rc_extension)
1012
- article_body = markdown.render(File.read procdoc)
1013
-
1014
- # Process with eRuby temaplte.
1015
- erb_template = ERB.new(File.read(@config["template"]), trim_mode: '%<>')
1016
- doc = erb_template.result(binding)
1017
-
1018
- doc
1019
- end
1020
- end
1021
-
1022
- class PbsKramdown < PBSimply
1023
- def initialize(config)
1024
- require 'kramdown'
1025
- super
1026
- end
1027
-
1028
- def print_fileproc_msg(filename)
1029
- STDERR.puts "#{filename} generate with Kramdown"
1030
- end
1031
-
1032
- def process_document(dir, filename, frontmatter, orig_filepath, ext, procdoc)
1033
- # Set feature options
1034
- features = @config["kramdown_features"] || {}
1035
-
1036
- # Getting HTML string.
1037
- markdown = Kramdown::Document.new(File.read(procdoc), **features)
1038
- article_body = markdown.to_html
1039
-
1040
- # Process with eRuby temaplte.
1041
- erb_template = ERB.new(File.read(@config["template"]), trim_mode: '%<>')
1042
- doc = erb_template.result(binding)
1043
-
1044
- doc
1045
- end
1046
- end
1047
-
1048
- class PbsCommonMark < PBSimply
1049
- def initialize(config)
1050
- require 'commonmarker'
1051
- super
1052
- end
1053
-
1054
- def print_fileproc_msg(filename)
1055
- STDERR.puts "#{filename} generate with CommonMarker (cmark-gfm)"
1056
- end
1057
-
1058
- def process_document(dir, filename, frontmatter, orig_filepath, ext, procdoc)
1059
- # Getting HTML string.
1060
- article_body = CommonMarker.render_doc(File.read(procdoc), :DEFAULT, [:table, :strikethrough]).to_html
1061
-
1062
- # Process with eRuby temaplte.
1063
- erb_template = ERB.new(File.read(@config["template"]), trim_mode: '%<>')
1064
- doc = erb_template.result(binding)
1065
-
1066
- doc
1067
- end
1068
- end
1069
-
1070
- end
1071
419
  end