oaktree 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 75d3078e95fe1e754b7ec050599c359309ac9987
4
+ data.tar.gz: e8922bf74025c7677559e57e930c38d75ac4ac6f
5
+ SHA512:
6
+ metadata.gz: 5bc2963c2cf05e06f91b260463b8f744ed60e10046044098abd75ce52660cfe42ec806f9e2d18d3fe450b87c7ca6f2de048603e02fc1ce1409307f3a4a406fe3
7
+ data.tar.gz: ef0ececc42e093219f6442aeb1a2f5485f06c4e119b5d22336942ce00dcedc68df73da2acb203900fdd1da451461e50fbdc52b4697356eb4b533182ba6f0c44e
data/COPYING ADDED
@@ -0,0 +1,6 @@
1
+ Copyright (C) 2012 Noel Cower <ncower@gmail.com>
2
+
3
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
4
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
5
+
6
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # OakTree
2
+
3
+ ## What in the nine hells is OakTree?
4
+
5
+ OakTree is an odd little static HTML blog gem thingy written by Noel R. Cower (hereinafter "yours truly" or just "I," because writing in the third person about my work is annoying). It is designed to fit my purposes - which is to say a very simple blog with very simple needs (has text, posts, some links, and my template). At present, it works moderately well at generating simple blogs – that is, HTML-only, no RSS feeds yet.
6
+
7
+ The entire interface for OakTree goes through its lone executable, `oak`. `oak` is intended to allow you to create posts, watch for changes, generate output HTML, and so on. If it's not obvious, this is all designed to be _very_ simple, because complex setups would be overkill.
8
+
9
+ ## How do I get all up in this OakTree?
10
+
11
+ For one, I apologize that I've used the phrase "get all up in X." For two, the only way to use OakTree is to download this and build the gem and install it yourself. This is actually fairly simple, provided I didn't commit something which opened a portal to hell. You really just want to do something like this:
12
+
13
+ $ rake package
14
+ $ cd pkg
15
+ $ gem install oaktree-<version>.gem
16
+ ... or, more likely ...
17
+ $ sudo gem install oaktree-<version>.gem
18
+
19
+ And, if you use rbenv, run `rbenv rehash` afterward, of course. I don't know what to tell you about RVM (aside from it being the destroyer of worlds, but that's another issue entirely), but you can figure it out. It's probably got a manpage or something.
20
+
21
+ After that, hop into some empty directory and run `oak init` to start a blog. At this point, you can being creating posts by either a) creating your own files in the _source/_ directory or using `oak newpost [title]` (where `[title]` is the title of your post). If you want to customize the blog's template, which I imagine you will, head on into _template/_ and begin tweaking _blog.mustache_. You will probably want to break things up into smaller template pieces to avoid getting bogged down with figuring out what's where in the mess of mustaches. For documentation on all available template tags, read _tags.md_. Finally, you'll want to configure your _blog_spec_ file. See _specs.md_ for info what options are available there, or just read _specification.rb_.
22
+
23
+ Once you've got all those things sorted out and you want to generate a blog, run `oak sync` in the same directory as your _blog_spec_ (I might do something git-ier later for that). This will build the HTML for your blog under _public/_. If you make further changes to the source files, you can run `oak sync` again and it will rebuild only files that need to be rebuilt (hopefully). Should you make changes to the template, you'll need to run `oak rebuild` to rebuild the entire blog (or delete the contents of _public/_), as `oak sync` does not account for template changes (it's a lot harder to figure out template dependencies accurately and doing it poorly seems like a bad idea, so I don't do it at all).
24
+
25
+ You may now commence uploading or using rsync or what have you to shunt your blog up somewhere. This may also be compatible with GitHub pages, but I'm not sure. Give it a shot.
26
+
27
+ ## What kind of license is OakTree under?
28
+
29
+ How kind of you to ask. OakTree is licensed under the WTFPL-2:
30
+
31
+ Copyright (C) 2012 Noel Cower <ncower@gmail.com>
32
+
33
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
34
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
35
+
36
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
37
+
38
+ If you have any questions about licensing, please direct them to the incinerator, because the license (also found in the _COPYING_ file that should have accompanied this repository) already answered those questions.
39
+
40
+ ## Anything else I should know?
41
+
42
+ No, but I'll reiterate this point again: **OakTree is unfinished, very unstable, and very much a work in progress.** If you attempt to make use of it, there's a good chance I cannot do anything to make your life better or help you, because you've already sealed your fate.
43
+
44
+ ## Why is it called 'OakTree?'
45
+
46
+ Because it's easy to type and `oak` is even easier to type. There is a possibility that the entire project will just be renamed to 'oak' just to make me happy and remove four characters that I would otherwise have to type.
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rubygems/package_task'
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+
5
+ spec = Gem::Specification.load 'oaktree.gemspec'
6
+
7
+ desc 'Run tests'
8
+ Rake::TestTask.new { |task|
9
+ task.libs << 'test'
10
+ files = FileList['test/**/test_*.rb'].to_a
11
+ task.test_files = files
12
+ }
13
+
14
+ desc "Build #{spec.name} #{spec.version.to_s}"
15
+ Gem::PackageTask.new(spec) { |task|
16
+ task.need_zip = true
17
+ }
18
+
19
+ CLEAN.include 'pkg'
20
+
21
+ task :default => :test
data/bin/oak ADDED
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'oaktree'
4
+ require 'fileutils'
5
+
6
+ # Constants
7
+
8
+ APP_NAME = 'oak'
9
+
10
+ GEM_NAME = 'oaktree'
11
+
12
+ HELP_TEXT = <<EOH
13
+ #{APP_NAME} <command> ...
14
+
15
+ COMMANDS
16
+ * -n, newpost [title]
17
+ Creates a new post. If no title is given, the post's title is "Untitled".
18
+ The new post is placed in the blog's source/ directory
19
+
20
+ * -i, init [dir]
21
+ Creates a 'blog_spec' file and required directories in the given directory
22
+ if blog_spec doesn't already exist. If no directory is provided, oak uses
23
+ the working directory.
24
+
25
+ * -s, sync
26
+ Builds HTML files for changed posts. This does not rebuild files following
27
+ template changes. If you've made changes to templates, you should use the
28
+ rebuild command.
29
+
30
+ * -r, rebuild
31
+ Builds HTML files for all posts, regardless of whether they've been changed.
32
+
33
+ * -v, version
34
+ Shows the version of oak and the oaktree gem.
35
+
36
+ * -h, help
37
+ Shows this help text.
38
+
39
+ EOH
40
+
41
+ BLOG_TEMPLATE = <<EOT
42
+ <!DOCTYPE html>
43
+ <html lang="en">
44
+ <head>
45
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
46
+ <title>{{blog_title}}{{#post}} &raquo; {{title}}{{/post}}</title>
47
+ </head>
48
+ <body>
49
+
50
+ <div id="header">
51
+ <h1 id="blog-title"><a href="{{blog_url}}index.html">{{blog_title}}</a></h1>
52
+ <p>
53
+ <span class="blog-description">{{blog_description}}<br/></span>
54
+ <span class="blog-byline">By {{blog_author}}</span>
55
+ </p>
56
+ </div><!-- title -->
57
+
58
+ <div id="posts">
59
+ {{#posts}}
60
+ <div class="post">
61
+ <div class="post-header">
62
+ <h2 class="post-title">
63
+ <a href="{{url}}">{{#source_link?}}&#x2192; {{/source_link?}}{{title}}</a>
64
+ </h2>
65
+ <span class="post-time">{{#time}}%-d %B %Y{{/time}}</span>
66
+ </div><!-- post-header -->
67
+ <div class="post-body">
68
+ {{{content}}}
69
+ </div><!-- post-body -->
70
+ </div><!-- post: {{title}} -->
71
+ {{/posts}}
72
+ </div><!-- posts -->
73
+
74
+ {{#paged?}}
75
+ <div id="nav-bar">
76
+ {{#has_previous?}}<a href="{{previous_url}}">&#x2190;
77
+ {{#archive}}{{#previous_archive}}{{#date}}%B %Y{{/date}}{{/previous_archive}}{{/archive}}{{^archive}}older{{/archive}}
78
+ </a>{{/has_previous?}}
79
+ {{#has_next?}}{{#has_previous?}} &mdash; {{/has_previous?}}{{/has_next?}}
80
+ {{#has_next?}}<a href="{{next_url}}">
81
+ {{#archive}}{{#next_archive}}{{#date}}%B %Y{{/date}}{{/next_archive}}{{/archive}}{{^archive}}newer{{/archive}}
82
+ &#x2192;</a>{{/has_next?}}
83
+
84
+ </div>
85
+ {{/paged?}}
86
+
87
+ <div id="archives">
88
+ <h4>Archives</h4>
89
+ <ul>
90
+ {{#archives}}
91
+ <li>
92
+ {{^open?}}<a href="{{permalink}}">{{/open?}}
93
+ {{#date}}%B %Y{{/date}}
94
+ {{^open?}}</a>{{/open?}}
95
+ </li>
96
+ {{/archives}}
97
+ </ul>
98
+ </div><!-- archives -->
99
+
100
+ <div class="copyright">
101
+ Copyright &copy; {{#today}}%Y{{/today}} {{blog_author}}. All rights reserved.
102
+ Made with <a href="https://github.com/nilium/oaktree">OakTree</a>.
103
+ </div><!-- copyright -->
104
+
105
+ </body>
106
+ </html>
107
+ EOT
108
+
109
+ RSS_FEED_TEMPLATE = <<EOT
110
+ <?xml version="1.0" encoding="UTF-8"?>
111
+ <rss version="2.0">
112
+ <channel>
113
+ <title>{{blog_title}}</title>
114
+ <link>{{blog_url}}</link>
115
+ <description>{{blog_description}}</description>
116
+ <pubDate>{{#today}}%a, %d %b %Y %T %z{{/today}}</pubDate>
117
+ <docs>http://www.rssboard.org/rss-2-0-1</docs>
118
+ {{#posts}}<item>
119
+ <title>{{title}}</title>
120
+ <link>{{permalink}}</link>
121
+ <description><![CDATA[{{{content}}}]]></description>
122
+ </item>{{/posts}}
123
+ </channel>
124
+ </rss>
125
+ EOT
126
+
127
+ # Commands
128
+
129
+ # Generate a new blog_spec and create 'source' and 'public' directories under
130
+ # the given directory.
131
+ def init_blog directory
132
+ if ! (File.exists?(directory) && File.directory?(directory)) && directory != '.'
133
+ FileUtils.mkdir_p directory
134
+ end
135
+
136
+ Dir.chdir(directory) { |path|
137
+ if File.exists? 'blog_spec' then
138
+ raise "blog_spec already exists in #{directory}"
139
+ end
140
+
141
+ Dir.mkdir 'source'
142
+ Dir.mkdir 'public'
143
+ Dir.mkdir 'template'
144
+
145
+ File.open('blog_spec', 'w') { |io| io.write OakTree::Specification.new.export_string }
146
+ File.open('template/blog.mustache', 'w') { |io| io.write BLOG_TEMPLATE }
147
+ File.open('template/rss_feed.mustache', 'w') { |io| io.write RSS_FEED_TEMPLATE }
148
+ }
149
+ end
150
+
151
+
152
+ # Show the version
153
+ def show_version
154
+ version = OakTree::VERSION
155
+
156
+ puts "#{GEM_NAME} version #{version} (ruby #{RUBY_VERSION} #{RUBY_PLATFORM} #{RUBY_RELEASE_DATE})"
157
+ end
158
+
159
+
160
+ # Show the help text for oak
161
+ def show_help
162
+ puts HELP_TEXT
163
+ show_version
164
+ end
165
+
166
+
167
+ # Generate a new post file for the blog
168
+ def new_post spec, title
169
+ title = title.gsub(/[\n\t]+/, '').strip
170
+
171
+ today = DateTime.now
172
+
173
+ titleslug = title.gsub(/[^_\w\s]/, '').strip.gsub(/\s+/, '_').downcase
174
+ timeslug = today.strftime '%Y-%m-%d'
175
+
176
+ file = "#{spec.sources_root}#{timeslug}_#{titleslug}.md"
177
+
178
+ head = <<HEADSTR
179
+ title: #{title}
180
+ time: #{today.strftime '%Y-%m-%d %H:%M:%S %z'}
181
+ ----
182
+
183
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
184
+ incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
185
+ nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
186
+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
187
+ fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
188
+ culpa qui officia deserunt mollit anim id est laborum.
189
+ HEADSTR
190
+
191
+ IO.write file, head
192
+
193
+ return file
194
+ end
195
+
196
+
197
+ def sync_changes spec, force
198
+ raise "No blog_spec found" if spec.nil?
199
+
200
+ blog = OakTree.new spec
201
+ blog.generate force
202
+ end
203
+
204
+ # Command selector majigger
205
+ def dispatch_command cmd, *args
206
+
207
+
208
+ spec = (File.exists? 'blog_spec') ? (OakTree::Specification.from_file 'blog_spec') : nil
209
+
210
+ case cmd
211
+
212
+ when 'newpost', '-n'
213
+ raise "Not in a blog directory" unless File.exists? 'blog_spec'
214
+
215
+ open_editor = false
216
+ title = ''
217
+
218
+ until args.empty?
219
+ arg = args.shift
220
+
221
+ # finnagling with arguments
222
+ if arg =~ /^(--[^=]+)=(.*)$/
223
+ arg = $1
224
+ args.unshift $2
225
+ end
226
+
227
+ if arg =~ /^(-[t])(.+)$/
228
+ arg = $1
229
+ args.unshift $2
230
+ end
231
+
232
+ case arg
233
+ when '-t', '--time'
234
+ output_dir = args.shift
235
+ break unless title.empty?
236
+ when '-e', '--edit'
237
+ open_editor = true
238
+ else
239
+ title = "#{title} #{arg}"
240
+ end
241
+ end
242
+
243
+ title = 'Untitled' if title.empty?
244
+ title = args.join ' ' unless args.empty?
245
+ file = new_post(spec, title)
246
+
247
+ if open_editor
248
+ editor = ENV['EDITOR'] || 'open'
249
+ %x["#{editor}" "#{file}"]
250
+ end
251
+
252
+ when 'init', '-i'
253
+ directory = '.'
254
+ directory = args[0] unless args.empty?
255
+ init_blog directory
256
+
257
+ when 'version', '-v'
258
+ show_version
259
+
260
+ when 'help', '-h'
261
+ show_help
262
+
263
+ when 'rebuild', '-r'
264
+ sync_changes spec, true
265
+
266
+ when 'sync', '-s'
267
+ sync_changes spec, false
268
+
269
+ else
270
+ puts "Unrecognized command '#{cmd}'\n\n"
271
+
272
+ show_help
273
+
274
+ end
275
+
276
+ end
277
+
278
+
279
+ # Main logic
280
+
281
+ if ARGV.empty? then
282
+ show_help
283
+ else
284
+ dispatch_command ARGV[0], *ARGV[1..ARGV.length]
285
+ end
@@ -0,0 +1,48 @@
1
+ require 'kramdown'
2
+ require 'kramdown/element'
3
+
4
+ class OakTree
5
+
6
+ module Kramdown
7
+
8
+ class OakHtml < ::Kramdown::Converter::Html
9
+
10
+ def convert_footnote el, indent
11
+ if @options[:auto_id_prefix]
12
+ el.options[:name] = @options[:auto_id_prefix] + el.options[:name]
13
+ end
14
+ super
15
+ end
16
+
17
+ def convert_div el, indent
18
+ "<#{el.type}#{html_attributes el.attr}>\n#{inner el, indent+1}\n</#{el.type}>"
19
+ end
20
+
21
+ def footnote_content
22
+ return '' if @footnotes.empty?
23
+
24
+ block = ::Kramdown::Element.new(:div, nil, {'class' => 'footnotes'})
25
+ block.children << (list = ::Kramdown::Element.new(:ol))
26
+
27
+ @footnotes.each { |fn_name, fn_elem|
28
+ item = ::Kramdown::Element.new(:li, nil, {'id' => "fn:#{fn_name}"})
29
+ # because we'll end up manipulating a child, we may as well do a deep copy
30
+ item.children = Marshal.load(Marshal.dump(fn_elem.children))
31
+
32
+ # basically what kramdown already does here
33
+ last = (last = item.children.last).type == :p ? last : (::Kramdown::Element.new(:p))
34
+ # yes, I'm using rev, shut up
35
+ last.children << (anchor = ::Kramdown::Element.new(:a, nil, {'href' => "#fnref:#{fn_name}", 'rev' => 'footnote'}))
36
+ anchor.children << ::Kramdown::Element.new(:raw, '&#8617;')
37
+
38
+ list.children << item
39
+ }
40
+
41
+ convert(block, 2)
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,236 @@
1
+ require 'date'
2
+ require 'digest'
3
+ require 'psych'
4
+
5
+ class OakTree ; end
6
+
7
+
8
+ # Contains the contents of a single post under the sources/ directory. Unlike
9
+ # past versions of PostData, this does not synchronize with the source file
10
+ # every time a member is accessed. It's assumed that what you got when you
11
+ # loaded the post is what you wanted and that any further changes must be
12
+ # explicitly synchronized.
13
+ #
14
+ class OakTree::PostData
15
+
16
+ attr_accessor :source_name
17
+ attr_accessor :source_path
18
+ # The path to the HTML file that's written when compiling the post.
19
+ attr_accessor :public_path
20
+ attr_accessor :title
21
+ attr_accessor :link
22
+ attr_accessor :permalink
23
+ attr_reader :time
24
+ # The post's slug, a filesystem- and URL-friendly string.
25
+ attr_reader :slug
26
+ # The post's body -- that is, the actual content of the blog post.
27
+ attr_accessor :content
28
+ # The time when the file was last read. Used to determine if sync_changes will
29
+ # reload the file. Defaults to the Unix epoch.
30
+ attr_accessor :last_read_time
31
+ # The kind of post this is. Expected to be either :post or :static, though
32
+ # the value is arbitrary.
33
+ attr_reader :kind
34
+ # The post's status. Either :published or :unpublished, though only :published
35
+ # holds meaning -- other values are assumed to be unpublished.
36
+ attr_reader :status
37
+ attr_accessor :hash
38
+ attr_accessor :spec
39
+
40
+ protected :source_path=, :public_path=, :title=, :link=, :content=,
41
+ :last_read_time=, :hash=, :spec=, :permalink=, :source_name=
42
+
43
+ # Loads a new post from the source file using the given Specification. The
44
+ # source file should be the full filename of the source file, but without any
45
+ # other path components.
46
+ #
47
+ def initialize(source_name, spec)
48
+ set_post_defaults
49
+
50
+ self.spec = spec
51
+
52
+ self.hash = nil
53
+ self.last_read_time = Time.at(0).to_datetime
54
+
55
+ self.source_name = source_name
56
+ self.source_path = File.absolute_path(source_name,
57
+ spec.sources_root).freeze()
58
+
59
+ raise "File doesn't exist: #{@source_path}" if ! File.exists? @source_path
60
+
61
+ sync_changes true
62
+ end # initialize
63
+
64
+ # Returns the default 'kind' a post should be. Typically means :post, though
65
+ # it may change in the future.
66
+ #
67
+ def self.default_kind
68
+ :post
69
+ end
70
+
71
+ # Returns the default 'status' a post should have. Typically :published, but
72
+ # this may change in the future.
73
+ #
74
+ def self.default_status
75
+ :published
76
+ end
77
+
78
+ # The regexp for identifying the line that separates the post head from the
79
+ # post body. This is typically three or more hyphens.
80
+ def self.metadata_separator
81
+ /^-{3,}\s*$/
82
+ end
83
+
84
+ # Synchronizes changes between the post's file and the post data object. If
85
+ # 'forced' is true (or non-false/nil), it will reload the file regardless of
86
+ # whether it's considered necessary.
87
+ #
88
+ # A "necessary" reload is when the file's hash changes or the file's last
89
+ # modification time is more recent than what the post data last read.
90
+ #
91
+ def sync_changes(forced = false)
92
+ raise "Source file does not exist." unless File.exists? @source_path
93
+
94
+ source_differs = !! forced
95
+ source_mtime = File.mtime(@source_path).to_datetime()
96
+ source_contents = File.open(@source_path, 'r') { |io| io.read }
97
+ source_hash = Digest::SHA1.hexdigest(source_contents).freeze()
98
+
99
+ if ! forced
100
+ # Check that the source differs from what was last checked.
101
+ source_differs = (hash != source_hash)
102
+ public_exists = (@public_path && File.exists?(@public_path))
103
+
104
+ # Check if the public file is older than the current source file.
105
+ if ! source_differs && public_exists
106
+ public_mtime = File.mtime(@public_path).to_datetime()
107
+
108
+ source_differs = true if public_mtime < source_mtime
109
+ end
110
+
111
+ if ! source_differs && @last_read_time < source_mtime
112
+ source_differs = true
113
+ end
114
+ end # ! forced
115
+
116
+ return if ! source_differs
117
+
118
+ self.last_read_time = source_mtime
119
+
120
+ # Reset the post's members to an unloaded state
121
+ set_post_defaults
122
+ self.hash = source_hash
123
+
124
+ source_split = source_contents.partition(self.class.metadata_separator)
125
+
126
+ load_header source_split[0]
127
+ self.content = source_split[2]
128
+
129
+ self
130
+ end # sync_changes
131
+
132
+ protected
133
+
134
+ # The regular expression used to fix post slugs. Basically matches groups of
135
+ # non-alphanumeric characters. This is used to replace slug-unfriendly chunks
136
+ # of new slugs with slug word separators.
137
+ #
138
+ def self.slug_fix_regexp
139
+ %r{(?:[^[:alnum:]] | [[:space:]])+}x
140
+ end
141
+
142
+ # Sets the default values for the post's data-related instance variables, so
143
+ # anything loaded during synchronization gets reset by this. Includes the
144
+ # post title, date, status, etc.
145
+ #
146
+ def set_post_defaults
147
+ self.public_path = nil
148
+ self.title = nil
149
+ self.link = nil
150
+ self.permalink = nil
151
+ self.time = nil
152
+ self.slug = ''.freeze()
153
+
154
+ self.kind = self.class.default_kind
155
+ self.status = self.class.default_status
156
+ end # set_post_defaults
157
+
158
+ # Reads the header from the header_source string into the post data. Currently
159
+ # uses Psych to parse the header as YAML.
160
+ #
161
+ def load_header(header_source)
162
+ begin
163
+ header_hash = Psych.load(header_source)
164
+ rescue Psych::SyntaxError => ex
165
+ puts "Failed to parse header for #{@source_name}"
166
+ raise
167
+ end
168
+
169
+ header_hash.each {
170
+ |key, value|
171
+ setter_sym = "#{key.to_s}=".to_sym
172
+ if self.respond_to?(setter_sym, true)
173
+ self.send setter_sym, value
174
+ else
175
+ raise "Invalid key/value for header: #{key} => #{value}."
176
+ end
177
+ }
178
+
179
+ self.slug = title if ! slug || slug.empty?
180
+
181
+ # Set the public HTML path and the permalink now that we have enough info
182
+ # about the post.
183
+ root = spec.blog_root
184
+ url = spec.base_url
185
+
186
+ link_path = String.new(spec.post_path)
187
+ link_path << @time.strftime(spec.date_path_format) if kind == :post
188
+ link_path << slug
189
+
190
+ self.public_path = "#{root}public/#{link_path}/index.html".freeze()
191
+ self.permalink = "#{url}#{link_path}"
192
+
193
+ nil
194
+ end # load_header
195
+
196
+ # Assigns a new time to the post -- the time must be a DateTime object or a
197
+ # String capable of being parsed by DateTime.parse.
198
+ def time=(new_time)
199
+ @time = if new_time
200
+ case new_time.class
201
+ when DateTime ; new_time
202
+ when String ; DateTime.parse new_time
203
+ else new_time.to_datetime
204
+ end
205
+ else
206
+ Time.at(0).to_datetime
207
+ end
208
+ end
209
+
210
+ # Sets the post slug, ensuring it's formatted properly.
211
+ def slug=(new_slug)
212
+ slug_temp = new_slug
213
+ if slug_temp
214
+ slug_temp = String.new(new_slug)
215
+ slug_temp.strip!
216
+
217
+ unless slug_temp.empty?
218
+ slug_temp.downcase!
219
+ slug_temp.gsub!(self.class.slug_fix_regexp, @spec.slug_separator)
220
+ end
221
+ else
222
+ slug_temp = ''
223
+ end
224
+
225
+ @slug = slug_temp.freeze()
226
+ end # slug=
227
+
228
+ def kind=(new_kind)
229
+ @kind = new_kind.to_sym
230
+ end
231
+
232
+ def status=(new_status)
233
+ @status = new_status.to_sym
234
+ end
235
+
236
+ end