PlainSite 1.2.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.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +43 -0
- data/LICENSE +20 -0
- data/PlainSite.gemspec +40 -0
- data/README.md +276 -0
- data/Rakefile +34 -0
- data/_config.yml +6 -0
- data/bin/plainsite +76 -0
- data/lib/PlainSite.rb +5 -0
- data/lib/PlainSite/Commands.rb +76 -0
- data/lib/PlainSite/Data/Category.rb +235 -0
- data/lib/PlainSite/Data/FrontMatterFile.rb +64 -0
- data/lib/PlainSite/Data/Post.rb +237 -0
- data/lib/PlainSite/Data/PostList.rb +164 -0
- data/lib/PlainSite/Data/PostListPage.rb +80 -0
- data/lib/PlainSite/RenderTask.rb +235 -0
- data/lib/PlainSite/Site.rb +330 -0
- data/lib/PlainSite/SocketPatch.rb +15 -0
- data/lib/PlainSite/Tpl/ExtMethods.rb +55 -0
- data/lib/PlainSite/Tpl/LayErb.rb +73 -0
- data/lib/PlainSite/Utils.rb +79 -0
- data/lib/PlainSite/_scaffold/_src/assets/README.md +5 -0
- data/lib/PlainSite/_scaffold/_src/assets/css/style.css +506 -0
- data/lib/PlainSite/_scaffold/_src/assets/favicon.ico +0 -0
- data/lib/PlainSite/_scaffold/_src/config.yml +10 -0
- data/lib/PlainSite/_scaffold/_src/data/essays/game-of-life.md +15 -0
- data/lib/PlainSite/_scaffold/_src/data/essays/phoenix-rebirth.html +15 -0
- data/lib/PlainSite/_scaffold/_src/data/programming/hello-world.md +48 -0
- data/lib/PlainSite/_scaffold/_src/extensions/TplExt.rb +23 -0
- data/lib/PlainSite/_scaffold/_src/routes.rb +49 -0
- data/lib/PlainSite/_scaffold/_src/templates/404.html +16 -0
- data/lib/PlainSite/_scaffold/_src/templates/about.html +11 -0
- data/lib/PlainSite/_scaffold/_src/templates/base.html +32 -0
- data/lib/PlainSite/_scaffold/_src/templates/header.html +8 -0
- data/lib/PlainSite/_scaffold/_src/templates/index.html +25 -0
- data/lib/PlainSite/_scaffold/_src/templates/list.html +41 -0
- data/lib/PlainSite/_scaffold/_src/templates/post.html +81 -0
- data/lib/PlainSite/_scaffold/_src/templates/rss.erb +29 -0
- data/test/CategoryTest.rb +63 -0
- data/test/FrontMatterFileTest.rb +40 -0
- data/test/LayErbTest.rb +20 -0
- data/test/ObjectProxyTest.rb +30 -0
- data/test/PostListTest.rb +55 -0
- data/test/PostTest.rb +48 -0
- data/test/SiteTest.rb +105 -0
- data/test/fixtures/2012-06-12-test.md +7 -0
- data/test/fixtures/category-demo/2012-06-12-post1.md +7 -0
- data/test/fixtures/category-demo/2013-05-01-post2.md +7 -0
- data/test/fixtures/category-demo/_meta.yml +1 -0
- data/test/fixtures/category-demo/index.md +6 -0
- data/test/fixtures/category-demo/sub-category1/sub-post1.md +1 -0
- data/test/fixtures/category-demo/sub-category2/sub-post2.md +1 -0
- data/test/fixtures/include.erb +1 -0
- data/test/fixtures/invalid-front-matter.html +7 -0
- data/test/fixtures/layout.erb +1 -0
- data/test/fixtures/no-front-matter.html +2 -0
- data/test/fixtures/tpl.erb +7 -0
- data/test/fixtures/valid-front-matter.html +14 -0
- data/test/runtest +6 -0
- metadata +202 -0
data/lib/PlainSite.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
module PlainSite
|
3
|
+
module Commands
|
4
|
+
require 'pp'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'commander/import'
|
7
|
+
require 'PlainSite/Site'
|
8
|
+
|
9
|
+
SELF_DIR=File.dirname(__FILE__)
|
10
|
+
|
11
|
+
def self.die(msg="\nExit now.\n")
|
12
|
+
$stderr.puts msg
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.run(action,args,opts)
|
17
|
+
root=opts.root || Dir.pwd
|
18
|
+
|
19
|
+
trap('INT') {self.die}
|
20
|
+
trap('TERM') {self.die}
|
21
|
+
|
22
|
+
unless File.exist? root
|
23
|
+
say_error "Site root directory does not exist:#{root}"
|
24
|
+
say_error "Create now? [Y/n]"
|
25
|
+
answer=$stdin.gets.strip.downcase # `agree` cannot set default answer
|
26
|
+
answer='y' if answer.empty?
|
27
|
+
if answer =='y'
|
28
|
+
FileUtils.mkdir_p root
|
29
|
+
else
|
30
|
+
self.die
|
31
|
+
end
|
32
|
+
end
|
33
|
+
root=File.realpath root
|
34
|
+
site=Site.new root
|
35
|
+
self.send action,site,args,opts
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.init(site,args,opts)
|
39
|
+
site.init_scaffold opts.override
|
40
|
+
puts 'Site scaffold init success!'
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.build(site,includes,opts)
|
44
|
+
site.build(dest:opts.dest,all:opts.all,local:opts.local,includes:includes)
|
45
|
+
puts 'Posts build finished.'
|
46
|
+
self.clean(site)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.clean(site,args=nil,opts=nil)
|
50
|
+
if site.isolated_files.empty?
|
51
|
+
puts "No isolated files found."
|
52
|
+
else
|
53
|
+
puts 'Do you really want to remove these isolated files?'
|
54
|
+
puts ((site.isolated_files.map {|f| f[(site.dest.length+1)..-1]}).join "\n")
|
55
|
+
puts "[y/N]"
|
56
|
+
answer=$stdin.gets.strip.downcase
|
57
|
+
answer='n' if answer.empty?
|
58
|
+
if answer =='y'
|
59
|
+
site.clean
|
60
|
+
puts 'Clean finished.'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.serve(site,args,opts)
|
66
|
+
site.serve(host:opts.host,port:opts.port)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.newpost(site,args,opts)
|
70
|
+
path=site.newpost args[0],args[1]
|
71
|
+
puts "New post created at:#{path}"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,235 @@
|
|
1
|
+
#coding:utf-8
|
2
|
+
module PlainSite;end
|
3
|
+
module PlainSite::Data
|
4
|
+
require 'safe_yaml'
|
5
|
+
require 'PlainSite/Data/Post'
|
6
|
+
require 'PlainSite/Data/PostList'
|
7
|
+
|
8
|
+
class ConflictNameException<Exception; end
|
9
|
+
|
10
|
+
# The Category directory class.
|
11
|
+
# The category directory can have its custom index post named 'index.md' or 'index.html'.
|
12
|
+
# File '_meta.yml' under category dir decribes it's meta info attributes.
|
13
|
+
# Supported attributes in '_meta.yml':
|
14
|
+
# display_name - The String category display name
|
15
|
+
class Category
|
16
|
+
META_FILE='_meta.yml'
|
17
|
+
attr_reader(
|
18
|
+
:path, # The String full path of category directory
|
19
|
+
:relpath, # The String path of category directory relative to site.data_path
|
20
|
+
:site,
|
21
|
+
:index, # The index Post
|
22
|
+
:has_index # Bool
|
23
|
+
)
|
24
|
+
# path - The String category directory abspath
|
25
|
+
# site - The Site belongs to
|
26
|
+
def initialize(path,site)
|
27
|
+
@path=path
|
28
|
+
@relpath=@path[(site.data_path.length+1)..-1] || ''
|
29
|
+
@site=site
|
30
|
+
@is_root = @relpath==''
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
# Alias of :relpath
|
35
|
+
alias :data_id :relpath
|
36
|
+
|
37
|
+
@index = self / :index
|
38
|
+
@has_index = !! @index
|
39
|
+
end
|
40
|
+
|
41
|
+
# whether this is the root category (aka site.data_path)
|
42
|
+
def root?
|
43
|
+
@is_root
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return parent category or nil if self is root
|
47
|
+
def parent
|
48
|
+
return @parent if @parent
|
49
|
+
return nil if root?
|
50
|
+
@parent=if @relpath['/']
|
51
|
+
Category.new File.join(@site.data_path,File.dirname(@relpath)),@site
|
52
|
+
else
|
53
|
+
Category.new @site.data_path,@site
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return parent categories array
|
58
|
+
def parents
|
59
|
+
return @parents if @parents
|
60
|
+
@parents=[]
|
61
|
+
return @parents if root?
|
62
|
+
cat=self
|
63
|
+
while cat=cat.parent
|
64
|
+
@parents.unshift cat
|
65
|
+
end
|
66
|
+
@parents
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
# Query data tree
|
71
|
+
# path - The String|Symbol *relative* path of post or category,
|
72
|
+
# It can be an exact file path,or a category/slug path.
|
73
|
+
# For examples,both 'essay/2011-11-11-live-happy.md' and 'essay/live-happy' are valid.
|
74
|
+
# '*' Retrieve all posts under this category.
|
75
|
+
# (But excludes its index post and includes sub category index posts).
|
76
|
+
# '**' retrieve recursively all posts under this category(include index posts)
|
77
|
+
# Category path(directory),example 'esssay',will return a new child Category .
|
78
|
+
# Category path ends with '/*',will return posts array under the category dir.
|
79
|
+
# Category path end with '/**',will return all posts under the category dir recursively.
|
80
|
+
# Return
|
81
|
+
# The sub Category when path is a category path
|
82
|
+
# The PostList when path is category path end with '/*' or '/**'
|
83
|
+
# The Post when path is a post path
|
84
|
+
def [](path)
|
85
|
+
path=path.to_s
|
86
|
+
if path['/']
|
87
|
+
return path.split('/').reduce(self) {|cat,p| cat && cat[p]}
|
88
|
+
end
|
89
|
+
|
90
|
+
return posts if path=='*'
|
91
|
+
return sub_posts if path=='**'
|
92
|
+
get_sub path or get_post path
|
93
|
+
|
94
|
+
end
|
95
|
+
alias :/ :[]
|
96
|
+
|
97
|
+
# Get post by slug or filename
|
98
|
+
# p - The String post slug or filename
|
99
|
+
# Return the Post or nil when not found
|
100
|
+
def get_post(p)
|
101
|
+
if p.to_sym==:index
|
102
|
+
p=(Post.extnames.map {|t|"index.#{t}"}).detect do |f|
|
103
|
+
File.exists? (File.join @path,f)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
return nil unless p
|
107
|
+
if p['.'] # filename
|
108
|
+
post_path=File.join @path,p
|
109
|
+
if (File.exists? post_path) && (File.basename(post_path)[0]!='_')
|
110
|
+
|
111
|
+
return Post.new post_path,@site
|
112
|
+
end
|
113
|
+
end
|
114
|
+
return posts_map[p] if posts_map.has_key? p
|
115
|
+
end
|
116
|
+
|
117
|
+
# The posts under this category,but exclude its index post.
|
118
|
+
# Not contains the sub category's posts but include sub category's index post.
|
119
|
+
# Default sort by date desc,slug asc
|
120
|
+
# Return PostList
|
121
|
+
def posts
|
122
|
+
return @posts if @posts
|
123
|
+
|
124
|
+
posts=Dir.glob(@path+'/*')
|
125
|
+
posts.select! do |f|
|
126
|
+
if File.basename(f)[0]=='_'
|
127
|
+
false
|
128
|
+
elsif File.directory? f # if directory has an index
|
129
|
+
(Category.new f,@site).has_index
|
130
|
+
else
|
131
|
+
(File.file? f) && (Post.extname_ok? f) &&
|
132
|
+
((File.basename f,(File.extname f))!='index') # exclude index post
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
return @posts=PostList.new([],@site) if posts.empty?
|
137
|
+
|
138
|
+
posts.map! {|f| Post.new f,@site}
|
139
|
+
|
140
|
+
@posts=PostList.new posts,@site
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# Get all posts under category recursively(include all index posts)
|
145
|
+
# Return PostList
|
146
|
+
def sub_posts
|
147
|
+
return @sub_posts if @sub_posts
|
148
|
+
all_posts=posts.to_a
|
149
|
+
subs.each do |c|
|
150
|
+
all_posts.concat c.sub_posts.to_a
|
151
|
+
end
|
152
|
+
all_posts.push @index if @has_index && root?
|
153
|
+
@sub_posts=PostList.new all_posts,@site
|
154
|
+
end
|
155
|
+
|
156
|
+
# Get the sub categories
|
157
|
+
# Return Category[]
|
158
|
+
def subs
|
159
|
+
|
160
|
+
return @subs if @subs
|
161
|
+
subs=Dir.glob @path+'/*'
|
162
|
+
subs.select! { |d| File.basename(d)[0]!='_' && (File.directory? d) }
|
163
|
+
subs.map! { |d| Category.new d,@site }
|
164
|
+
|
165
|
+
@subs=subs
|
166
|
+
end
|
167
|
+
|
168
|
+
# Get the sub category
|
169
|
+
# sub_path The String path relative to current category
|
170
|
+
# Return Category or nil
|
171
|
+
def get_sub(sub_path)
|
172
|
+
return nil if sub_path[0]=='_'
|
173
|
+
sub_path=File.join @path,sub_path
|
174
|
+
if File.directory? sub_path
|
175
|
+
Category.new sub_path,@site
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
# The Hash map of slug=>post
|
181
|
+
# Return Hash
|
182
|
+
def posts_map
|
183
|
+
return @posts_map if @posts_map
|
184
|
+
|
185
|
+
group=posts.group_by &:slug
|
186
|
+
|
187
|
+
conflicts=group.reject {|k,v| v.length==1} #Filter unconflicts
|
188
|
+
unless conflicts.empty?
|
189
|
+
msg=(conflicts.map {|slug,v|
|
190
|
+
"#{slug}:\n\t"+(v.map &:path).join("\n\t")
|
191
|
+
}).join "\n"
|
192
|
+
raise ConflictNameException,"These posts use same slug:#{msg}"
|
193
|
+
end
|
194
|
+
|
195
|
+
group.merge!(group) {|k,v| v[0] }
|
196
|
+
@posts_map=group
|
197
|
+
end
|
198
|
+
|
199
|
+
# Get the meta info in category path ./_meta.yml
|
200
|
+
def meta_info
|
201
|
+
return @meta_info if @meta_info
|
202
|
+
meta_file=File.join(@path,META_FILE)
|
203
|
+
if File.exists? meta_file
|
204
|
+
@meta_info = YAML.safe_load_file meta_file
|
205
|
+
else
|
206
|
+
@meta_info= {}
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# The String category dir name,root category's name is empty string
|
211
|
+
def name
|
212
|
+
return @name unless @name.nil?
|
213
|
+
@name=File.basename(data_id)
|
214
|
+
end
|
215
|
+
|
216
|
+
def display_name
|
217
|
+
return @display_name unless @display_name.nil?
|
218
|
+
@display_name= if meta_info['display_name'].nil?
|
219
|
+
name.gsub(/-|_/,' ').capitalize
|
220
|
+
else
|
221
|
+
meta_info['display_name']
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def to_s
|
226
|
+
'Category:'+(@relpath.empty? ? '#root#' : @relpath)
|
227
|
+
end
|
228
|
+
alias :to_str :to_s
|
229
|
+
|
230
|
+
def to_a
|
231
|
+
parents+[self]
|
232
|
+
end
|
233
|
+
alias :to_ary :to_a
|
234
|
+
end # end class Category
|
235
|
+
end # end PlainSite::Data
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#coding:utf-8
|
2
|
+
module PlainSite;end
|
3
|
+
module PlainSite::Data
|
4
|
+
# require 'active_support/core_ext/hash' # Silly
|
5
|
+
# require 'active_support/hash_with_indifferent_access' # Fat
|
6
|
+
require 'safe_yaml'
|
7
|
+
class InvalidFrontMatterFileException<Exception;end
|
8
|
+
|
9
|
+
class FrontMatterFile
|
10
|
+
# YAML Front Matter File
|
11
|
+
# Example file content:
|
12
|
+
# ---
|
13
|
+
# title: Hello,world!
|
14
|
+
# tags: [C,Java,Ruby,Haskell]
|
15
|
+
# ---
|
16
|
+
# File content Here!
|
17
|
+
#
|
18
|
+
attr_reader :path
|
19
|
+
DELIMITER='---'
|
20
|
+
|
21
|
+
def initialize(path)
|
22
|
+
# The String file path
|
23
|
+
@path=path
|
24
|
+
@content_pos=0
|
25
|
+
end
|
26
|
+
|
27
|
+
def headers
|
28
|
+
File.open(@path) do |f|
|
29
|
+
line=f.readline.strip
|
30
|
+
break if line!=DELIMITER
|
31
|
+
header_lines=[]
|
32
|
+
begin
|
33
|
+
while (line=f.readline.strip)!=DELIMITER
|
34
|
+
header_lines.push line
|
35
|
+
end
|
36
|
+
@headers = YAML.safe_load(header_lines.join "\n")
|
37
|
+
unless Hash===@headers
|
38
|
+
raise InvalidFrontMatterFileException,"Front YAML must be Hash,not #{@headers.class},in file: #{path}"
|
39
|
+
end
|
40
|
+
@content_pos=f.pos
|
41
|
+
@headers['path'] = @path
|
42
|
+
return @headers
|
43
|
+
rescue YAML::SyntaxError => e
|
44
|
+
raise InvalidFrontMatterFileException,"YAML SyntaxError:#{e.message},in file: #{path}"
|
45
|
+
rescue EOFError => e
|
46
|
+
raise InvalidFrontMatterFileException,"Unclosed YAML in file: #{path}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
return {"path" => @path }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Intended no cache, listen directory changes not work on platforms other than linux
|
54
|
+
def content
|
55
|
+
self.headers # init @content_pos
|
56
|
+
File.open(path) do |f|
|
57
|
+
f.seek @content_pos,IO::SEEK_SET
|
58
|
+
@content=f.read.strip.freeze
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
end
|
64
|
+
end # end PlainSite::Data
|
@@ -0,0 +1,237 @@
|
|
1
|
+
#coding:utf-8
|
2
|
+
module PlainSite;end
|
3
|
+
module PlainSite::Data
|
4
|
+
require 'pygments'
|
5
|
+
require 'kramdown'
|
6
|
+
require 'date'
|
7
|
+
require 'securerandom'
|
8
|
+
require 'PlainSite/Data/FrontMatterFile'
|
9
|
+
require 'PlainSite/Tpl/LayErb'
|
10
|
+
require 'PlainSite/Data/Category'
|
11
|
+
|
12
|
+
class Post
|
13
|
+
attr_reader(
|
14
|
+
:slug, # The String slug of post,default is it's filename without date and dot-extname
|
15
|
+
:path, # The String post file path
|
16
|
+
:relpath, # The String path relative to site.data_path
|
17
|
+
:data_id, # The String data id,format:'category/sub-category/slug'
|
18
|
+
:category_path, # The String category path
|
19
|
+
:filename, # The String filename
|
20
|
+
:site,
|
21
|
+
:is_index # The Bool to indicate if this is an index post.
|
22
|
+
)
|
23
|
+
|
24
|
+
attr_accessor( #These properties are inited by others
|
25
|
+
:prev_post, # The Post previous,set by PostList
|
26
|
+
:next_post, # The Post next,set by PostList
|
27
|
+
)
|
28
|
+
|
29
|
+
DATE_NAME_RE=/^(\d{4})-(\d{1,2})-(\d{1,2})-(.+)$/
|
30
|
+
HIGHLIGHT_RE=/<highlight(\s+[^>]+)?\s*>(.+?)<\/highlight>/m
|
31
|
+
MARKDOWN_CODE_RE=/```(.+?)```/m
|
32
|
+
|
33
|
+
|
34
|
+
# Init a post
|
35
|
+
# path - The String file abs path,at present,only support '.html' and '.md' extname.
|
36
|
+
# site - The Site this post belongs to
|
37
|
+
def initialize(path,site)
|
38
|
+
if File.directory? path # create as category index post
|
39
|
+
path=(@@extnames.map {|x| File.join path+'/index'+x}).detect {|f| File.exists? f}
|
40
|
+
@is_index=true
|
41
|
+
end
|
42
|
+
@site=site
|
43
|
+
@path=path
|
44
|
+
@relpath=@path[(site.data_path.length+1)..-1]
|
45
|
+
|
46
|
+
@filename=File.basename @path
|
47
|
+
@extname=File.extname @filename
|
48
|
+
if DATE_NAME_RE =~ @filename
|
49
|
+
@date=Date.new $1.to_i,$2.to_i,$3.to_i
|
50
|
+
@slug=File.basename $4,@extname
|
51
|
+
end
|
52
|
+
@slug=File.basename @filename,@extname unless @slug
|
53
|
+
|
54
|
+
@category_path=File.dirname(@relpath)
|
55
|
+
|
56
|
+
if @category_path=='/' or @category_path=='.'
|
57
|
+
@category_path=''
|
58
|
+
@data_id=@slug
|
59
|
+
else
|
60
|
+
@data_id=File.join @category_path,@slug
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
# The Date of post
|
65
|
+
def date
|
66
|
+
return @date if @date
|
67
|
+
@date=get_date 'date'
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_date(k)
|
71
|
+
date=post_file.headers[k]
|
72
|
+
|
73
|
+
date=if String===date
|
74
|
+
begin Date.parse(date) rescue Date.today end
|
75
|
+
elsif Date===date
|
76
|
+
date
|
77
|
+
else
|
78
|
+
Date.today
|
79
|
+
end
|
80
|
+
end
|
81
|
+
private :get_date
|
82
|
+
|
83
|
+
def updated_date
|
84
|
+
return @updated_date if @updated_date
|
85
|
+
@updated_date=get_date 'updated_date'
|
86
|
+
end
|
87
|
+
|
88
|
+
# The Category this post belongs to
|
89
|
+
def category
|
90
|
+
return @category if @category
|
91
|
+
@category=Category.new File.join(@site.data_path,@category_path),@site
|
92
|
+
end
|
93
|
+
|
94
|
+
# The String content type of post,default is it's extname without dot
|
95
|
+
def content_type
|
96
|
+
return @content_type if @content_type
|
97
|
+
@content_type=post_file.headers['content_type']
|
98
|
+
@content_type=@extname[1..-1] if @content_type.nil?
|
99
|
+
@content_type
|
100
|
+
end
|
101
|
+
|
102
|
+
# The Boolean value indicates if this post is a draft,default is false,alias is `draft?`
|
103
|
+
# def draft
|
104
|
+
# return @draft unless @draft.nil?
|
105
|
+
# @draft=!!post_file.headers['draft']
|
106
|
+
# end
|
107
|
+
# alias :draft? :draft
|
108
|
+
|
109
|
+
# def deleted
|
110
|
+
# return @deleted unless @deleted.nil?
|
111
|
+
# @deleted=!!post_file.headers['deleted']
|
112
|
+
# end
|
113
|
+
# alias :deleted? :deleted
|
114
|
+
|
115
|
+
# Private
|
116
|
+
def post_file
|
117
|
+
return @post_file if @post_file
|
118
|
+
@post_file=FrontMatterFile.new @path
|
119
|
+
end
|
120
|
+
private :post_file
|
121
|
+
|
122
|
+
# Post file raw content
|
123
|
+
def raw_content
|
124
|
+
post_file.content
|
125
|
+
end
|
126
|
+
|
127
|
+
# Rendered html content
|
128
|
+
# It must render highlight code first.
|
129
|
+
# Highlight syntax:
|
130
|
+
# Html tag style:
|
131
|
+
# <highlight>puts 'Hello'</highlight>
|
132
|
+
# With line numbers and language
|
133
|
+
# <highlight ruby linenos>puts 'Hello'</highlight>
|
134
|
+
# Set line number start from 10
|
135
|
+
# <highlight ruby linenos=10>puts 'Hello'</highlight>
|
136
|
+
# Highlight lines
|
137
|
+
# <highlight ruby linenos hl_lines=1>puts 'Hello'</highlight>
|
138
|
+
#
|
139
|
+
# Highlight html tag options:
|
140
|
+
# linenos - If provide,output will contains line number
|
141
|
+
# linenos=Int - Line number start from,default is 1
|
142
|
+
# If no new line in code,the output will be inline nowrap style and no linenos.
|
143
|
+
#
|
144
|
+
# You can also use markdown style code block,e.g. ```puts 'Code'```.
|
145
|
+
# But code this style doesn't rendered by Pygments.
|
146
|
+
# You need to load a browser side renderer,viz. SyntaxHighlighter.
|
147
|
+
#
|
148
|
+
# Then render erb template,context is post itself,you can access self and self.site methods
|
149
|
+
#
|
150
|
+
# Return the String html content
|
151
|
+
def content
|
152
|
+
# quick fix,when build local, current path will change
|
153
|
+
p=@path + "::"+ @site._cur_page_dir
|
154
|
+
|
155
|
+
post_content=raw_content.dup
|
156
|
+
# stash highlight code
|
157
|
+
codeMap={}
|
158
|
+
post_content.gsub! HIGHLIGHT_RE do
|
159
|
+
placeholder='-HIGHLIGHT '+SecureRandom.uuid+' ENDHIGHLIGHT-'
|
160
|
+
attrs=$1
|
161
|
+
attrs=attrs.split " "
|
162
|
+
lexer=attrs.shift || ""
|
163
|
+
attrs=Hash[attrs.map {|v| v.split "="}]
|
164
|
+
attrs["hl_lines"]=(attrs["hl_lines"] || "").split ","
|
165
|
+
code=$2
|
166
|
+
codeMap[placeholder]={
|
167
|
+
lexer: lexer,
|
168
|
+
linenos: (attrs.key? "linenos") ? 'table' : false ,
|
169
|
+
linenostart: attrs["linenos"] || 1,
|
170
|
+
hl_lines: attrs["hl_lines"],
|
171
|
+
code: code.strip,
|
172
|
+
nowrap: code["\n"].nil?
|
173
|
+
}
|
174
|
+
placeholder
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
# Then render erb template if needed
|
179
|
+
if post_content['<%'] && !post_file.headers['disable_erb']
|
180
|
+
post_content=PlainSite::Tpl::LayErb.render_s post_content,self
|
181
|
+
end
|
182
|
+
|
183
|
+
post_content=self.class.content_to_html post_content,content_type
|
184
|
+
|
185
|
+
#put back code
|
186
|
+
codeMap.each do |k,v|
|
187
|
+
code=Pygments.highlight v[:code],lexer: v[:lexer],formatter: 'html',options:{
|
188
|
+
linenos: v[:linenos],
|
189
|
+
linenostart: v[:linenostart],
|
190
|
+
nowrap: v[:nowrap],
|
191
|
+
hl_lines: v[:hl_lines],
|
192
|
+
startinline: v[:lexer] == 'php'
|
193
|
+
}
|
194
|
+
code="<code class=\"highlight\">#{code}</code>" if v[:nowrap]
|
195
|
+
post_content[k]=code # String#sub method has a hole of back reference
|
196
|
+
end
|
197
|
+
|
198
|
+
return post_content
|
199
|
+
end
|
200
|
+
|
201
|
+
# The String url of this post in site
|
202
|
+
def url
|
203
|
+
@site.url_for @data_id
|
204
|
+
end
|
205
|
+
|
206
|
+
# You can use method call to access post file front-matter data
|
207
|
+
def respond_to?(name)
|
208
|
+
return true if post_file.headers.key? name.to_s
|
209
|
+
super
|
210
|
+
end
|
211
|
+
|
212
|
+
def method_missing(name,*args,&block)
|
213
|
+
if args.length==0 && block.nil? && post_file.headers.key?(name.to_s)
|
214
|
+
return post_file.headers[name.to_s]
|
215
|
+
end
|
216
|
+
super
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.content_to_html(content,content_type)
|
220
|
+
if content_type=='md'
|
221
|
+
content=Kramdown::Document.new(content,input:'GFM').to_html
|
222
|
+
end
|
223
|
+
content
|
224
|
+
end
|
225
|
+
|
226
|
+
@@extnames=['md','html']
|
227
|
+
|
228
|
+
def self.extnames
|
229
|
+
@@extnames
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.extname_ok?(f)
|
233
|
+
@@extnames.include? File.extname(f)[1..-1]
|
234
|
+
end
|
235
|
+
|
236
|
+
end # class Post
|
237
|
+
end # module PlainSite::Data
|