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.
@@ -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