PlainSite 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|