PlainSite 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +43 -0
  4. data/LICENSE +20 -0
  5. data/PlainSite.gemspec +40 -0
  6. data/README.md +276 -0
  7. data/Rakefile +34 -0
  8. data/_config.yml +6 -0
  9. data/bin/plainsite +76 -0
  10. data/lib/PlainSite.rb +5 -0
  11. data/lib/PlainSite/Commands.rb +76 -0
  12. data/lib/PlainSite/Data/Category.rb +235 -0
  13. data/lib/PlainSite/Data/FrontMatterFile.rb +64 -0
  14. data/lib/PlainSite/Data/Post.rb +237 -0
  15. data/lib/PlainSite/Data/PostList.rb +164 -0
  16. data/lib/PlainSite/Data/PostListPage.rb +80 -0
  17. data/lib/PlainSite/RenderTask.rb +235 -0
  18. data/lib/PlainSite/Site.rb +330 -0
  19. data/lib/PlainSite/SocketPatch.rb +15 -0
  20. data/lib/PlainSite/Tpl/ExtMethods.rb +55 -0
  21. data/lib/PlainSite/Tpl/LayErb.rb +73 -0
  22. data/lib/PlainSite/Utils.rb +79 -0
  23. data/lib/PlainSite/_scaffold/_src/assets/README.md +5 -0
  24. data/lib/PlainSite/_scaffold/_src/assets/css/style.css +506 -0
  25. data/lib/PlainSite/_scaffold/_src/assets/favicon.ico +0 -0
  26. data/lib/PlainSite/_scaffold/_src/config.yml +10 -0
  27. data/lib/PlainSite/_scaffold/_src/data/essays/game-of-life.md +15 -0
  28. data/lib/PlainSite/_scaffold/_src/data/essays/phoenix-rebirth.html +15 -0
  29. data/lib/PlainSite/_scaffold/_src/data/programming/hello-world.md +48 -0
  30. data/lib/PlainSite/_scaffold/_src/extensions/TplExt.rb +23 -0
  31. data/lib/PlainSite/_scaffold/_src/routes.rb +49 -0
  32. data/lib/PlainSite/_scaffold/_src/templates/404.html +16 -0
  33. data/lib/PlainSite/_scaffold/_src/templates/about.html +11 -0
  34. data/lib/PlainSite/_scaffold/_src/templates/base.html +32 -0
  35. data/lib/PlainSite/_scaffold/_src/templates/header.html +8 -0
  36. data/lib/PlainSite/_scaffold/_src/templates/index.html +25 -0
  37. data/lib/PlainSite/_scaffold/_src/templates/list.html +41 -0
  38. data/lib/PlainSite/_scaffold/_src/templates/post.html +81 -0
  39. data/lib/PlainSite/_scaffold/_src/templates/rss.erb +29 -0
  40. data/test/CategoryTest.rb +63 -0
  41. data/test/FrontMatterFileTest.rb +40 -0
  42. data/test/LayErbTest.rb +20 -0
  43. data/test/ObjectProxyTest.rb +30 -0
  44. data/test/PostListTest.rb +55 -0
  45. data/test/PostTest.rb +48 -0
  46. data/test/SiteTest.rb +105 -0
  47. data/test/fixtures/2012-06-12-test.md +7 -0
  48. data/test/fixtures/category-demo/2012-06-12-post1.md +7 -0
  49. data/test/fixtures/category-demo/2013-05-01-post2.md +7 -0
  50. data/test/fixtures/category-demo/_meta.yml +1 -0
  51. data/test/fixtures/category-demo/index.md +6 -0
  52. data/test/fixtures/category-demo/sub-category1/sub-post1.md +1 -0
  53. data/test/fixtures/category-demo/sub-category2/sub-post2.md +1 -0
  54. data/test/fixtures/include.erb +1 -0
  55. data/test/fixtures/invalid-front-matter.html +7 -0
  56. data/test/fixtures/layout.erb +1 -0
  57. data/test/fixtures/no-front-matter.html +2 -0
  58. data/test/fixtures/tpl.erb +7 -0
  59. data/test/fixtures/valid-front-matter.html +14 -0
  60. data/test/runtest +6 -0
  61. metadata +202 -0
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ #coding:utf-8
3
+ module PlainSite
4
+ VERSION='1.2.0'
5
+ end
@@ -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