pbsimply 2.0.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/ruby
2
+ require 'erb'
3
+ require 'yaml'
4
+
5
+ module PBSimply::Frontmatter
6
+ # Read Frontmatter from the document.
7
+ # This method returns frontmatter, pos.
8
+ # pos means position at end of Frontmatter on the file.
9
+ def read_frontmatter(dir, filename)
10
+ frontmatter = nil
11
+ pos = nil
12
+
13
+ source_path = File.join(dir, filename)
14
+
15
+ if File.exist? File.join(dir, ".meta." + filename)
16
+ # Load standalone metadata YAML.
17
+ frontmatter = Psych.unsafe_load(File.read(File.join(dir, (".meta." + filename))))
18
+ pos = 0
19
+ else
20
+
21
+ case File.extname filename
22
+ when ".md"
23
+
24
+ # Load Markdown's YAML frontmatter.
25
+ File.open(source_path) do |f|
26
+ l = f.gets
27
+ next unless l && l.chomp == "---"
28
+
29
+ lines = []
30
+
31
+ while l = f.gets
32
+ break if l.nil?
33
+
34
+ break if l.chomp == "---"
35
+ lines.push l
36
+ end
37
+
38
+ next if f.eof?
39
+
40
+ begin
41
+ frontmatter = Psych.unsafe_load(lines.join)
42
+ rescue => e
43
+ STDERR.puts "!CRITICAL: Cannot parse frontmatter."
44
+ raise e
45
+ end
46
+
47
+ pos = f.pos
48
+ end
49
+
50
+ when ".rst"
51
+ # ReSTRUCTURED Text
52
+
53
+ File.open(source_path) do |f|
54
+ l = f.gets
55
+ if l =~ /:([A-Za-z_-]+): (.*)/ #docinfo
56
+ frontmatter = { $1 => [$2.chomp] }
57
+ last_key = $1
58
+
59
+ # Read docinfo
60
+ while(l = f.gets)
61
+ break if l =~ /^\s*$/ # End of docinfo
62
+ if l =~ /^\s+/ # Continuous line
63
+ docinfo_lines.last.push($'.chomp)
64
+ elsif l =~ /:([A-Za-z_-]+): (.*)/
65
+ frontmatter[$1] = [$2.chomp]
66
+ last_key = $1
67
+ end
68
+ end
69
+
70
+ # Treat docinfo lines
71
+ frontmatter.each do |k,v|
72
+ v = v.join(" ")
73
+ #if((k == "author" || k == "authors") && v.include?(";")) # Multiple authors.
74
+ if(v.include?(";")) # Multiple element.
75
+ v = v.split(/\s*;\s*/)
76
+
77
+ elsif k == "date" # Date?
78
+ # Datetime?
79
+ if v =~ /[0-2][0-9]:[0-6][0-9]/
80
+ v = Time.parse(v)
81
+ else
82
+ v = Date.parse(v)
83
+ end
84
+ elsif v == "yes" || v == "true"
85
+ v = true
86
+ else # Simple String.
87
+ nil # keep v
88
+ end
89
+
90
+ frontmatter[k] = v
91
+ end
92
+
93
+ elsif l && l.chomp == ".." #YAML
94
+ # Load ReST YAML that document begins comment and block is yaml.
95
+ lines = []
96
+
97
+ while(l = f.gets)
98
+ if(l !~ /^\s*$/ .. l =~ /^\s*$/)
99
+ if l=~ /^\s*$/
100
+ break
101
+ else
102
+ lines.push l
103
+ end
104
+ end
105
+ end
106
+ next if f.eof?
107
+
108
+
109
+ # Rescue for failed to read YAML.
110
+ begin
111
+ frontmatter = Psych.unsafe_load(lines.map {|i| i.sub(/^\s*/, "") }.join)
112
+ rescue
113
+ STDERR.puts "Error in parsing ReST YAML frontmatter (#{$!})"
114
+ next
115
+ end
116
+ else
117
+ next
118
+ end
119
+
120
+ pos = f.pos
121
+
122
+ end
123
+ end
124
+ end
125
+
126
+ abort "This document has no frontmatter" unless frontmatter
127
+ abort "This document has no title." unless frontmatter["title"]
128
+
129
+ outext = frontmatter["force_ext"] || ".html"
130
+ outpath = case
131
+ when @outfile
132
+ @outfile
133
+ when @accs_processing
134
+ File.join(@config["outdir"], @dir, "index") + outext
135
+ else
136
+ File.join(@config["outdir"], @dir, File.basename(filename, ".*")) + outext
137
+ end
138
+
139
+ absolute_current = File.absolute_path Dir.pwd
140
+ absolute_docdir = File.absolute_path dir
141
+ absolute_docpath = File.absolute_path source_path
142
+ pwd_length = absolute_current.length
143
+
144
+ ### Additional meta values. ###
145
+ frontmatter["source_directory"] = dir # Source Directory
146
+ frontmatter["source_filename"] = filename # Source Filename
147
+ frontmatter["source_path"] = source_path # Source Path
148
+ frontmatter["dest_path"] = outpath
149
+ frontmatter["normalized_docdir"] = absolute_docdir[pwd_length..]
150
+ frontmatter["normalized_docpath"] = absolute_docpath[pwd_length..]
151
+ # URL in site.
152
+ this_url = (source_path).sub(/^[\.\/]*/) { @config["self_url_prefix"] || "/" }.sub(/\.[a-zA-Z0-9]+$/, ".html")
153
+ frontmatter["page_url"] = this_url
154
+ # URL in site with URI encode.
155
+ frontmatter["page_url_encoded"] = ERB::Util.url_encode(this_url)
156
+ frontmatter["page_url_encoded_external"] = ERB::Util.url_encode((source_path).sub(/^[\.\/]*/) { @config["self_url_external_prefix"] || "/" }.sub(/\.[a-zA-Z0-9]+$/, ".html"))
157
+ frontmatter["page_html_escaped"] = ERB::Util.html_escape(this_url)
158
+ frontmatter["page_html_escaped_external"] = ERB::Util.html_escape((source_path).sub(/^[\.\/]*/) { @config["self_url_external_prefix"] || "/" }.sub(/\.[a-zA-Z0-9]+$/, ".html"))
159
+ # Title with URL Encoded.
160
+ frontmatter["title_encoded"] = ERB::Util.url_encode(frontmatter["title"])
161
+ frontmatter["title_html_escaped"] = ERB::Util.html_escape(frontmatter["title"])
162
+ fts = frontmatter["timestamp"]
163
+ fts = fts.to_datetime if Time === fts
164
+ if DateTime === fts
165
+ frontmatter["timestamp_xmlschema"] = fts.xmlschema
166
+ frontmatter["timestamp_jplocal"] = fts.strftime('%Y年%m月%d日 %H時%M分%S秒')
167
+ frontmatter["timestamp_rubytimestr"] = fts.strftime('%a %b %d %H:%M:%S %Z %Y')
168
+ frontmatter["timestamp_str"] = fts.strftime("%Y-%m-%d %H:%M:%S %Z")
169
+ elsif Date === fts
170
+ frontmatter["timestamp_xmlschema"] = fts.xmlschema
171
+ frontmatter["timestamp_jplocal"] = fts.strftime('%Y年%m月%d日')
172
+ frontmatter["timestamp_rubytimestr"] = fts.strftime('%a %b %d')
173
+ frontmatter["timestamp_str"] = fts.strftime("%Y-%m-%d")
174
+ elsif Date === frontmatter["Date"]
175
+ fts = frontmatter["Date"]
176
+ frontmatter["timestamp_xmlschema"] = fts.xmlschema
177
+ frontmatter["timestamp_jplocal"] = fts.strftime('%Y年%m月%d日')
178
+ frontmatter["timestamp_rubytimestr"] = fts.strftime('%a %b %d')
179
+ frontmatter["timestamp_str"] = fts.strftime("%Y-%m-%d")
180
+ end
181
+
182
+ fsize = FileTest.size(source_path)
183
+ mtime = File.mtime(source_path).to_i
184
+
185
+ frontmatter["_filename"] ||= filename
186
+ frontmatter["pagetype"] ||= "post"
187
+
188
+ frontmatter["_size"] = fsize
189
+ frontmatter["_mtime"] = mtime
190
+ frontmatter["_last_proced"] = @now.to_i
191
+
192
+ if File.extname(filename) == ".md"
193
+ frontmatter["_docformat"] = "Markdown"
194
+ elsif File.extname(filename) == ".rst" || File.extname(filename) == ".rest"
195
+ frontmatter["_docformat"] = "ReST"
196
+ elsif File.extname(filename) == ".rdoc"
197
+ frontmatter["_docformat"] = "RDoc"
198
+ end
199
+
200
+ frontmatter["date"] ||= @now.strftime("%Y-%m-%d %H:%M:%S")
201
+
202
+ return frontmatter, pos
203
+ end
204
+ end
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # Hooks is new in PureBuilder Simply 2.2.
4
+ # Hooks object has instance variables for each timing.
5
+ #
6
+ class PBSimply::Hooks
7
+
8
+ # Timing object class.
9
+ class HooksHolder
10
+ def initialize name
11
+ @name = name
12
+ @hooks = []
13
+ end
14
+
15
+ def <<(proc)
16
+ @hooks << proc
17
+ end
18
+
19
+ alias :add :<<
20
+
21
+ def run(arg)
22
+ STDERR.puts "Hooks processing (#{@name})"
23
+ @hooks.each_with_index do |proc, index|
24
+ STDERR.puts "Hooks[#{index}]"
25
+ begin
26
+ proc.(arg)
27
+ rescue
28
+ STDERR.puts "*** HOOKS PROC ERROR ***"
29
+ raise
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def initialize(pbsimply)
36
+ @pbsimply = pbsimply
37
+
38
+ # Called first phase before generate. This hooks called before blessing.
39
+ #
40
+ # Argument: frontmatter, procdoc.
41
+ # procdoc is processing source document path.
42
+ @pre = HooksHolder.new "pre"
43
+
44
+ # Called after document was generated.
45
+ #
46
+ # Argument: outpath, frontmatter, procdoc.
47
+ # outpath is generated final document path. You can read output result.
48
+ # procdoc is source document path before generate.
49
+ @process = HooksHolder.new "process"
50
+
51
+ # Called each deleted document on ACCS final phase, before deletion.
52
+ #
53
+ # Argument: target_file_path, source_file_path.
54
+ # target_file_path is output file path (existing or non-existing.)
55
+ @delete = HooksHolder.new "delete"
56
+
57
+ # Called after all document were generated.
58
+ #
59
+ # Argument: this_time_processed([{source, dest, frontmatter}...])
60
+ # this_time_processed has actually processed documents.
61
+ # source is source file path, dest is generated file path.
62
+ @post = HooksHolder.new "post"
63
+
64
+ # Called before generating ACCS index.
65
+ #
66
+ # Argument: index, indexes.
67
+ #
68
+ # index is @index (frontmatter for ACCS index),
69
+ # indexes is @indexes.
70
+ @accs = HooksHolder.new "accs"
71
+ end
72
+
73
+ def load
74
+ if File.file?("./.pbsimply-hooks.rb")
75
+ require './.pbsimply-hooks.rb'
76
+ PBSimply::Hooks.load_hooks(self)
77
+ end
78
+ end
79
+
80
+ attr :pre
81
+ attr :process
82
+ attr :delete
83
+ attr :post
84
+ attr :accs
85
+ end
@@ -0,0 +1,77 @@
1
+ # Document tweaking module.
2
+ # Provides deprected Pre-plugins and Post-plugins.
3
+ module PBSimply::Plugger
4
+ POST_PROCESSORS = {
5
+ ".rb" => "ruby",
6
+ ".pl" => "perl",
7
+ ".py" => "python",
8
+ ".lua" => "lua",
9
+ ".bash" => "bash",
10
+ ".zsh" => "zsh",
11
+ ".php" => "php",
12
+ ".sed" => ["sed", ->(script, target) { ["-f", script, target] } ]
13
+ }
14
+
15
+ # Deprecated filter command feature.
16
+ #
17
+ # Pre plugins invoke each command on .pre_generate directory as filter command before process document.
18
+ # Porcessing document (same content as source document) path given as first argument.
19
+ def pre_plugins(procdoc, frontmatter)
20
+ if File.directory?(".pre_generate")
21
+ STDERR.puts("Processing with pre plugins")
22
+ script_file = File.join(".pre_generate", script_file)
23
+ Dir.entries(".pre_generate").sort.each do |script_file|
24
+ next if script_file =~ /^\./
25
+ STDERR.puts "Running script: #{File.basename script_file}"
26
+ pre_script_result = nil
27
+ script_cmdline = case
28
+ when File.executable?(script_file)
29
+ [script_file, procdoc]
30
+ when POST_PROCESSORS[File.extname(script_file)]
31
+ [POST_PROCESSORS[File.extname(script_file)], script_file, procdoc]
32
+ else
33
+ ["perl", script_file, procdoc]
34
+ end
35
+ IO.popen({"pbsimply_doc_frontmatter" => YAML.dump(frontmatter)}, script_cmdline) do |io|
36
+ pre_script_result = io.read
37
+ end
38
+ File.open(procdoc, "w") {|f| f.write pre_script_result}
39
+ end
40
+ end
41
+ end
42
+
43
+ # Post plugins invoke each command on .post_generate directory as filter command after processed.
44
+ # Generated document (typically HTML) path given as first argument.
45
+ def post_plugins(frontmatter=nil)
46
+ if File.directory?(".post_generate")
47
+
48
+ STDERR.puts("Processing with post plugins")
49
+
50
+ @this_time_processed.each do |v|
51
+ STDERR.puts "Processing #{v[:dest]} (from #{v[:source]})"
52
+ procdoc = v[:dest]
53
+ frontmatter ||= @indexes[File.basename v[:source]]
54
+ File.open(@workfile_frontmatter, "w") {|f| f.write JSON_LIB.dump(frontmatter)}
55
+ Dir.entries(".post_generate").sort.each do |script_file|
56
+ next if script_file =~ /^\./
57
+ STDERR.puts "Running script: #{script_file}"
58
+ script_file = File.join(".post_generate", script_file)
59
+ post_script_result = nil
60
+ script_cmdline = case
61
+ when File.executable?(script_file)
62
+ [script_file, procdoc]
63
+ when POST_PROCESSORS[File.extname(script_file)]
64
+ [POST_PROCESSORS[File.extname(script_file)], script_file, procdoc]
65
+ else
66
+ ["perl", script_file, procdoc]
67
+ end
68
+ IO.popen({"pbsimply_workdir" => @workdir,"pbsimply_frontmatter" => @workfile_frontmatter, "pbsimply_indexes" => @db.path}, script_cmdline) do |io|
69
+ post_script_result = io.read
70
+ end
71
+
72
+ File.open(procdoc, "w") {|f| f.write post_script_result}
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # Module for BLESSING feature.
4
+ module PBSimply::Prayer
5
+ def bless frontmatter
6
+ if @config["bless_style"] == "cmd"
7
+ bless_cmd frontmatter
8
+ else
9
+ bless_ruby frontmatter
10
+ end
11
+ end
12
+
13
+ def bless_ruby(frontmatter)
14
+ # BLESSING (Always)
15
+ if PureBuilder.const_defined?(:BLESS) && Proc === PureBuilder::BLESS
16
+ begin
17
+ PureBuilder::BLESS.(frontmatter, self)
18
+ rescue
19
+ STDERR.puts "*** BLESSING PROC ERROR ***"
20
+ raise
21
+ end
22
+ end
23
+
24
+ # BLESSING (ACCS)
25
+ if @accs && PureBuilder::ACCS.const_defined?(:BLESS) && Proc === PureBuilder::ACCS::BLESS
26
+ begin
27
+ PureBuilder::ACCS::BLESS.(frontmatter, self)
28
+ rescue
29
+ STDERR.puts "*** ACCS BLESSING PROC ERROR ***"
30
+ raise
31
+ end
32
+ end
33
+
34
+ # ACCS DEFINITIONS
35
+ if @accs
36
+ if Proc === PureBuilder::ACCS::DEFINITIONS[:next]
37
+ i = PureBuilder::ACCS::DEFINITIONS[:next].call(frontmatter, self)
38
+ frontmatter["next_article"] = i if i
39
+ end
40
+ if Proc === PureBuilder::ACCS::DEFINITIONS[:prev]
41
+ i = PureBuilder::ACCS::DEFINITIONS[:prev].call(frontmatter, self)
42
+ frontmatter["prev_article"] = i if i
43
+ end
44
+ end
45
+
46
+ autobless(frontmatter)
47
+ end
48
+
49
+ def bless_cmd(frontmatter)
50
+ File.open(@workfile_frontmatter, "w") {|f| f.write JSON_LIB.dump(frontmatter) }
51
+ # BLESSING (Always)
52
+ if @config["bless_cmd"]
53
+ (Array === @config["bless_cmd"] ? system(*@config["bless_cmd"]) : system(@config["bless_cmd"]) ) or abort "*** BLESS COMMAND RETURNS NON-ZERO STATUS"
54
+ end
55
+ # BLESSING (ACCS)
56
+ if @config["bless_accscmd"]
57
+ (Array === @config["bless_accscmd"] ? system({"pbsimply_workdir" => @workdir, "pbsimply_frontmatter" => @workfile_frontmatter, "pbsimply_indexes" => @db.path}, *@config["bless_accscmd"]) : system({"pbsimply_workdir" => @workdir, "pbsimply_frontmatter" => @workfile_frontmatter, "pbsimply_indexes" => @db.path}, @config["bless_accscmd"]) ) or abort "*** BLESS COMMAND RETURNS NON-ZERO STATUS"
58
+ end
59
+ mod_frontmatter = JSON.load(File.read(@workfile_frontmatter))
60
+ frontmatter.replace(mod_frontmatter)
61
+
62
+ autobless(frontmatter)
63
+ end
64
+
65
+ # Blessing automatic method with configuration.
66
+ def autobless(frontmatter)
67
+ catch(:accs_rel) do
68
+ # find Next/Prev page on accs
69
+ if @accs && @config["blessmethod_accs_rel"]
70
+ # Preparing. Run at once.
71
+ if !@article_order
72
+ @rev_article_order_index = {}
73
+
74
+ case @config["blessmethod_accs_rel"]
75
+ when "numbering"
76
+ @article_order = @indexes.to_a.sort_by {|i| i[1]["_filename"].to_i }
77
+ when "date"
78
+ begin
79
+ @article_order = @indexes.to_a.sort_by {|i| i[1]["date"]}
80
+ rescue
81
+ abort "*** Automatic Blessing Method Error: Maybe some article have no date."
82
+ end
83
+ when "timestamp"
84
+ begin
85
+ @article_order = @indexes.to_a.sort_by {|i| i[1]["timestamp"]}
86
+ rescue
87
+ abort "*** Automatic Blessing Method Error: Maybe some article have no timetsamp."
88
+ end
89
+ when "lexical"
90
+ @article_order = @indexes.to_a.sort_by {|i| i[1]["_filename"]}
91
+ end
92
+ @article_order.each_with_index {|x,i| @rev_article_order_index[x[0]] = i }
93
+ end
94
+
95
+ throw(:accs_rel) unless index = @rev_article_order_index[frontmatter["_filename"]]
96
+ if @article_order[index + 1]
97
+ frontmatter["next_article"] = {"url" => @article_order[index + 1][1]["page_url"],
98
+ "title" => @article_order[index + 1][1]["title"]}
99
+ end
100
+ if index > 0
101
+ frontmatter["prev_article"] = {"url" => @article_order[index - 1][1]["page_url"],
102
+ "title" => @article_order[index - 1][1]["title"]}
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end