writecast 0.0.1
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.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/TODO +44 -0
- data/bin/writecast +28 -0
- data/lib/writecast.rb +7 -0
- data/lib/writecast/commands/help.rb +52 -0
- data/lib/writecast/commands/init.rb +14 -0
- data/lib/writecast/commands/override.rb +14 -0
- data/lib/writecast/commands/process.rb +106 -0
- data/lib/writecast/commands/serve.rb +31 -0
- data/lib/writecast/config.rb +13 -0
- data/lib/writecast/models.rb +233 -0
- data/lib/writecast/version.rb +3 -0
- data/lib/writecast/views/all_tags_index.rb +24 -0
- data/lib/writecast/views/base.rb +90 -0
- data/lib/writecast/views/index.rb +51 -0
- data/lib/writecast/views/month_index.rb +22 -0
- data/lib/writecast/views/page.rb +18 -0
- data/lib/writecast/views/post.rb +63 -0
- data/lib/writecast/views/tag_index.rb +26 -0
- data/lib/writecast/views/year_index.rb +21 -0
- data/writecast.gemspec +29 -0
- metadata +203 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/TODO
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
Write some docs with examples - e.g. how to run server locally, recommended workflow etc.
|
2
|
+
Add comment form to posts and pages (as another GEM ?)
|
3
|
+
\-Add rack script to email on comments posted to it from a form in the page.
|
4
|
+
Script will simply respond to HTTP POST requests forwarded: nginx ---*socket*---> unicorn -> script "/comment" (also '/contact'?)
|
5
|
+
Fix views to use Erector::Widget::Page, and lose the 'rawtext's
|
6
|
+
Put a copy of jquery + friends(form,validate), and a simple stylesheet in an static dir, which is copied out on init.
|
7
|
+
Try to use CSS animations rather than a GIF for comment loading.
|
8
|
+
|
9
|
+
Much, much later:
|
10
|
+
Add Alt text to links in tag cloud - list how many articles.
|
11
|
+
Consider Writecast::separate() which takes an array of strings and returns a single string with the SEPARATOR inserted between arguments. It may not be worth it, see models.rb.
|
12
|
+
Allow multi-level pages.
|
13
|
+
Move to_atom (and any other common functionality) in models.rb to a mixin.
|
14
|
+
Clean up views/base.rb render_menu logic.
|
15
|
+
Search all code for magic values - add these to the config!
|
16
|
+
Tests?
|
17
|
+
\-Try inserting images with names including ']'
|
18
|
+
|
19
|
+
Done:
|
20
|
+
Show recent posts on front page.
|
21
|
+
Show summaries on month indices.
|
22
|
+
Display time information on posts.
|
23
|
+
Handle images in filename_static
|
24
|
+
Fix images to work correctly on index page.
|
25
|
+
Handle posts+pages with no yaml header.
|
26
|
+
Display tags correctly on posts, and in tag cloud on front page.
|
27
|
+
Add date to Pages.
|
28
|
+
Fix URLs with proper encoding.
|
29
|
+
Put configuration info in config.rb
|
30
|
+
Use Writecast:: namespace.
|
31
|
+
Code syntax highlighting.
|
32
|
+
document.rb Index.to_html TODO
|
33
|
+
Reconsider the layout of the site. It would be nice to have less 'tagged-*' folders in root.
|
34
|
+
URL encode tag directories.
|
35
|
+
Create /init directory to copy files for initial site setup.
|
36
|
+
Make app use rack interface.
|
37
|
+
Allow use of unicode filenames and titles for posts and pages.
|
38
|
+
Integrate server into program? -- this is handled by thin now.
|
39
|
+
Package as Gem.
|
40
|
+
Write init function which will set up given folder for site.
|
41
|
+
Add homepage (github) to gemspec.
|
42
|
+
Generate an RSS feed for each tag and for the entire index.
|
43
|
+
Allow overriding page, post, index views. do check in views/base and add command "override 'views/whatever'".
|
44
|
+
Add a font to CSS?
|
data/bin/writecast
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# load the local config file if any
|
4
|
+
require_relative File.join(Dir.pwd, 'config.rb') if File.exist? 'config.rb'
|
5
|
+
require_relative '../lib/writecast'
|
6
|
+
|
7
|
+
def Writecast::abort
|
8
|
+
Kernel::abort "Invalid command. Try 'writecast help' for usage info."
|
9
|
+
end
|
10
|
+
|
11
|
+
if (1..2).include? ARGV.length
|
12
|
+
case ARGV[0]
|
13
|
+
when 'help'
|
14
|
+
Writecast::help
|
15
|
+
when 'init'
|
16
|
+
Writecast::init ARGV[1] || Dir.pwd
|
17
|
+
when 'process'
|
18
|
+
Writecast::process ARGV[1] || Dir.pwd
|
19
|
+
when 'serve'
|
20
|
+
Writecast::serve ARGV[1] || Dir.pwd
|
21
|
+
when 'override'
|
22
|
+
Writecast::override ARGV[1]
|
23
|
+
else
|
24
|
+
Writecast::abort
|
25
|
+
end
|
26
|
+
else
|
27
|
+
Writecast::abort
|
28
|
+
end
|
data/lib/writecast.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# each command is a different process which will require its own libraries
|
2
|
+
require_relative 'writecast/commands/process'
|
3
|
+
require_relative 'writecast/commands/init'
|
4
|
+
require_relative 'writecast/commands/serve'
|
5
|
+
require_relative 'writecast/commands/help'
|
6
|
+
require_relative 'writecast/commands/override'
|
7
|
+
require_relative 'writecast/version'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support/core_ext/string'
|
3
|
+
|
4
|
+
def Writecast::help
|
5
|
+
|
6
|
+
def em string
|
7
|
+
"\033[1m#{string}\033[m"
|
8
|
+
end
|
9
|
+
|
10
|
+
puts <<-DOC.strip_heredoc
|
11
|
+
|
12
|
+
#{em'NAME'}
|
13
|
+
writecast - a slightly opinionated static website generator.
|
14
|
+
|
15
|
+
#{em'SYNOPSIS'}
|
16
|
+
#{em'writecast help'}
|
17
|
+
#{em'writecast init'} [target_directory]
|
18
|
+
#{em'writecast process'} [target_directory]
|
19
|
+
#{em'writecast serve'} [target_directory]
|
20
|
+
#{em'writecast override'} target_view
|
21
|
+
|
22
|
+
#{em'DESCRIPTION'}
|
23
|
+
#{em'writecast'} is a static website generator. It is slightly
|
24
|
+
'opinionated' in that it uses a few conventions in order to make
|
25
|
+
publishing a simple website easy.
|
26
|
+
|
27
|
+
#{em'EXAMPLES'}
|
28
|
+
#{em'writecast help'}
|
29
|
+
Display this help documentation.
|
30
|
+
|
31
|
+
#{em'writecast init'} ~/my_new_blog
|
32
|
+
Initialise the directory structure below '~/my_new_blog'.
|
33
|
+
|
34
|
+
#{em'writecast process'} ~/my_new_blog
|
35
|
+
...
|
36
|
+
#{em'writecast serve'} ~/my_new_blog
|
37
|
+
...
|
38
|
+
#{em'writecast override'} views/index
|
39
|
+
...
|
40
|
+
|
41
|
+
#{em'OVERVIEW'}
|
42
|
+
|
43
|
+
#{em'DEFAULTS'}
|
44
|
+
|
45
|
+
#{em'ERROR REPORTING'}
|
46
|
+
|
47
|
+
#{em'EXIT STATUS'}
|
48
|
+
|
49
|
+
#{em'HISTORY'}
|
50
|
+
|
51
|
+
DOC
|
52
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
def Writecast::init destination
|
2
|
+
Kernel.abort "Destination must be a directory" unless File.ftype(destination) == "directory"
|
3
|
+
puts "Initialising site in '#{destination}'"
|
4
|
+
p Dir.pwd
|
5
|
+
puts "creating 'content/pages'"
|
6
|
+
FileUtils.mkdir_p(File.join(destination, 'content', 'pages'))
|
7
|
+
puts "creating 'content/posts'"
|
8
|
+
FileUtils.mkdir_p(File.join(destination, 'content', 'posts'))
|
9
|
+
puts "creating 'content/static'"
|
10
|
+
FileUtils.mkdir_p(File.join(destination, 'content', 'static'))
|
11
|
+
puts "creating 'public_html/'"
|
12
|
+
FileUtils.mkdir_p(File.join(destination, 'public_html'))
|
13
|
+
puts "done."
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
def Writecast::override view_path
|
2
|
+
file_path = File.join(File.dirname(__FILE__), '..', "#{view_path}.rb")
|
3
|
+
if File.exist? file_path
|
4
|
+
local_file_path = File.join(Dir.pwd, "#{view_path}.rb")
|
5
|
+
if File.exist? local_file_path
|
6
|
+
Kernel.abort 'view already overridden'
|
7
|
+
else
|
8
|
+
FileUtils.mkdir_p File.join(Dir.pwd, 'views')
|
9
|
+
FileUtils.cp(file_path, local_file_path)
|
10
|
+
end
|
11
|
+
else
|
12
|
+
Kernel.abort 'no such view'
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require_relative '../config'
|
2
|
+
require_relative '../models'
|
3
|
+
|
4
|
+
def Writecast::process dir
|
5
|
+
|
6
|
+
Dir::chdir dir
|
7
|
+
|
8
|
+
FileUtils.rm_r(Dir["public_html/*"]) # clean old public_html
|
9
|
+
FileUtils.mkdir("public_html") unless Dir.exist?("public_html")
|
10
|
+
FileUtils.cp_r('content/static', 'public_html/static') # set up new public_html's static content
|
11
|
+
|
12
|
+
# store posts in a nested hash by date, and in a hash by tag
|
13
|
+
posts_hash = Hash.new{|h,k| h[k]=Hash.new(&h.default_proc)}
|
14
|
+
tags_hash = {}
|
15
|
+
num_posts = 0
|
16
|
+
Dir["content/posts/**/*"].each do |post_raw|
|
17
|
+
if File.ftype(post_raw) == 'file' && Writecast::DOC_EXTS.include?(File.extname(post_raw).downcase)
|
18
|
+
post = Writecast::Post.new(post_raw)
|
19
|
+
|
20
|
+
if posts_hash[post.time.year][post.time.month].empty?
|
21
|
+
posts_hash[post.time.year][post.time.month] = [post]
|
22
|
+
else
|
23
|
+
posts_hash[post.time.year][post.time.month] << post
|
24
|
+
end
|
25
|
+
|
26
|
+
post.tags and post.tags.each do |tag|
|
27
|
+
escaped_tag = CGI.escape(tag)
|
28
|
+
if tags_hash[[tag,escaped_tag]]
|
29
|
+
tags_hash[[tag,escaped_tag]] << post
|
30
|
+
else
|
31
|
+
tags_hash[[tag,escaped_tag]] = [post]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# create public_html folder structure
|
36
|
+
post_path = File.dirname(post.file_path)
|
37
|
+
FileUtils.mkdir_p("public_html#{post_path}")
|
38
|
+
|
39
|
+
# copy static files associated with post, if any
|
40
|
+
if Dir.exist?("#{post_raw}_static")
|
41
|
+
FileUtils.cp_r(
|
42
|
+
"#{post_raw}_static",
|
43
|
+
"public_html#{post_path}/#{File.basename(post_raw)}_static"
|
44
|
+
)
|
45
|
+
end
|
46
|
+
num_posts += 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# posts_hash is passed to the index so that recent posts and yearly archives can be built.
|
51
|
+
index = Writecast::Index.new(posts_hash, tags_hash, num_posts)
|
52
|
+
# generate index atom feed
|
53
|
+
File.open("public_html/feed.atom", "w") {|file| file.write(index.to_atom)}
|
54
|
+
|
55
|
+
pages = [index]
|
56
|
+
Dir["content/pages/**/*"].each do |page_raw|
|
57
|
+
if File.ftype(page_raw) == 'file' && Writecast::DOC_EXTS.include?(File.extname(page_raw).downcase)
|
58
|
+
page = Writecast::Page.new(page_raw)
|
59
|
+
pages << page
|
60
|
+
|
61
|
+
page_path = File.dirname(page.file_path)
|
62
|
+
if Dir.exist?("#{page_raw}_static")
|
63
|
+
FileUtils.cp_r(
|
64
|
+
"#{page_raw}_static",
|
65
|
+
"public_html#{page_path}/#{File.basename(page_raw)}_static"
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
pages.each do |page|
|
71
|
+
File.open("public_html/#{page.file_path}", "w") {|file| file.write(page.to_html(pages))}
|
72
|
+
end
|
73
|
+
|
74
|
+
posts_hash.each do |year,month_hash|
|
75
|
+
|
76
|
+
# write year indices
|
77
|
+
File.open("public_html/#{year}/index.html", "w") do |file|
|
78
|
+
file.write(Writecast::Year_index.new(year, month_hash).to_html(pages))
|
79
|
+
end
|
80
|
+
|
81
|
+
month_hash.each do |month,month_posts|
|
82
|
+
# write month indices
|
83
|
+
File.open("public_html/#{year}/#{"%02d" % month}/index.html", "w") do |file|
|
84
|
+
file.write(Writecast::Month_index.new(year, month, month_posts).to_html(pages))
|
85
|
+
end
|
86
|
+
|
87
|
+
# write posts
|
88
|
+
month_posts.each do |post|
|
89
|
+
File.open("public_html/#{post.file_path}", "w") {|file| file.write(post.to_html(pages))}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
FileUtils.mkdir_p("public_html/tags")
|
95
|
+
tags_hash.each do |tag_set, posts|
|
96
|
+
tag,escaped_tag = tag_set
|
97
|
+
FileUtils.mkdir_p("public_html/tags/#{tag}")
|
98
|
+
tag_index = Writecast::Tag_index.new(tag_set, posts)
|
99
|
+
File.open("public_html/tags/#{tag}/index.html", "w") {|file| file.write(tag_index.to_html(pages))}
|
100
|
+
# generate tag atom feed
|
101
|
+
File.open("public_html/tags/#{tag}/feed.atom", "w") {|file| file.write(tag_index.to_atom)}
|
102
|
+
end
|
103
|
+
File.open("public_html/tags/index.html", "w") do |file|
|
104
|
+
file.write(Writecast::All_tags_index.new(tags_hash).to_html(pages))
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
def Writecast::serve dir
|
4
|
+
|
5
|
+
rack_app = Proc.new { |env|
|
6
|
+
|
7
|
+
root = File.join(dir, 'public_html')
|
8
|
+
default_charset = "; charset=utf-8"
|
9
|
+
|
10
|
+
# Extract the requested path from the request
|
11
|
+
path = Rack::Utils.unescape(env['PATH_INFO'])
|
12
|
+
index_file = root + "#{path}/index.html"
|
13
|
+
|
14
|
+
if File.exists?(index_file)
|
15
|
+
# Return the index
|
16
|
+
[200, {'Content-Type' => 'text/html' + default_charset}, [File.read(index_file)]]
|
17
|
+
else
|
18
|
+
# Pass the request to the directory app
|
19
|
+
response = Rack::Directory.new(root).call(env)
|
20
|
+
if response[1]['Content-Type']
|
21
|
+
response[1]['Content-Type'] += default_charset
|
22
|
+
end
|
23
|
+
response
|
24
|
+
end
|
25
|
+
}
|
26
|
+
|
27
|
+
Rack::Server.new(:app => rack_app,
|
28
|
+
:Port => 3000,
|
29
|
+
:server => 'webrick'
|
30
|
+
).start
|
31
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Writecast
|
2
|
+
# Site configuration
|
3
|
+
SITE_TITLE ||= "my site title (change this)"
|
4
|
+
SEPARATOR ||= " / "
|
5
|
+
INDEX_TITLE ||= "blog"
|
6
|
+
NUM_RECENT_POSTS ||= 5
|
7
|
+
DOC_EXTS ||= ['.post', '.page', '.txt'] # must be lower case
|
8
|
+
PRETTY_HTML = true unless defined? PRETTY_HTML
|
9
|
+
KRAMDOWN_OPTS = {:coderay_line_numbers => :table, :coderay_css => :class}
|
10
|
+
# Atom feed configuration
|
11
|
+
SITE_URI ||= "http://mydomain.net"
|
12
|
+
SITE_AUTHOR ||= "Anonymous Coward"
|
13
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'uuidtools'
|
3
|
+
require 'atom'
|
4
|
+
require_relative 'views/base'
|
5
|
+
|
6
|
+
module Writecast
|
7
|
+
|
8
|
+
# represents a document; either a post or a page.
|
9
|
+
class Document
|
10
|
+
|
11
|
+
attr_reader :title, :time, :summary, :body, :tags, :link_path, :file_path, :raw_file_path
|
12
|
+
|
13
|
+
def initialize(file_path)
|
14
|
+
doc_string = IO.read(file_path)
|
15
|
+
@raw_file_path = file_path
|
16
|
+
|
17
|
+
if doc_string =~ /(\A---\n(.*)---\n)?(.*)/m
|
18
|
+
doc_header = ($2 ? YAML.load($2) : {}) || {}
|
19
|
+
summary,body = $3.split("\n\n",2)
|
20
|
+
else
|
21
|
+
raise ArgumentError, "Parse error in #{file_path}."
|
22
|
+
end
|
23
|
+
|
24
|
+
file_basename = File.basename(file_path)
|
25
|
+
|
26
|
+
@title = doc_header['title'] || file_basename.chomp(File.extname(file_path))
|
27
|
+
@time = doc_header['time'] || File.stat(file_path).mtime
|
28
|
+
@tags = doc_header['tags']
|
29
|
+
|
30
|
+
if summary
|
31
|
+
# the first regex matches kramdown image syntax, and stores two backreferences
|
32
|
+
# the second just matches the syntax
|
33
|
+
image_regex_select = /(!\[.*\]\()(.*)\)/
|
34
|
+
image_regex = /!\[.*\]\(.*\)/
|
35
|
+
|
36
|
+
# swap all relative image paths with absolute paths
|
37
|
+
image_swaps = {}
|
38
|
+
summary.scan(image_regex_select).zip(summary.scan(image_regex)) do |ab,image_match|
|
39
|
+
a,b = ab
|
40
|
+
image_swaps[image_match] = "#{a}/#{"%04d" % @time.year}/" +
|
41
|
+
"#{"%02d" % @time.month}/#{file_basename}_static/" +
|
42
|
+
"#{File.basename(b)})"
|
43
|
+
end
|
44
|
+
@summary = summary.gsub(image_regex, image_swaps).chomp
|
45
|
+
else
|
46
|
+
@summary = ''
|
47
|
+
end
|
48
|
+
|
49
|
+
@body = body ? body.chomp : ''
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Post < Document
|
54
|
+
|
55
|
+
include UUIDTools
|
56
|
+
|
57
|
+
attr_reader :id
|
58
|
+
|
59
|
+
def initialize(file_path)
|
60
|
+
super
|
61
|
+
path_dir = ["/%04d" % @time.year, "/%02d" % @time.month, "/%02d-" % @time.day].join
|
62
|
+
@link_path = path_dir + "%s.html" % CGI.escape(@title)
|
63
|
+
@file_path = path_dir + "%s.html" % @title
|
64
|
+
@id = "urn:uuid:#{UUID.sha1_create(UUID_URL_NAMESPACE, SITE_URI + @link_path).to_s}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_html(pages)
|
68
|
+
title_tag = SITE_TITLE +
|
69
|
+
@time.strftime("#{SEPARATOR}%Y#{SEPARATOR}%B#{SEPARATOR}%d#{SEPARATOR}") + @title
|
70
|
+
Post_view.new(:doc => self, :pages => pages, :title_tag => title_tag
|
71
|
+
).to_html(:prettyprint => PRETTY_HTML)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Page < Document
|
76
|
+
|
77
|
+
def initialize(file_path)
|
78
|
+
super
|
79
|
+
@link_path = "/%s.html" % CGI.escape(@title)
|
80
|
+
@file_path = "/%s.html" % @title
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_html(pages)
|
84
|
+
title_tag = SITE_TITLE + SEPARATOR + @title
|
85
|
+
Page_view.new(:doc => self, :pages => pages, :title_tag => title_tag
|
86
|
+
).to_html(:prettyprint => PRETTY_HTML)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Index < Document
|
91
|
+
|
92
|
+
include UUIDTools
|
93
|
+
|
94
|
+
def initialize(posts_hash, tags_hash, num_posts)
|
95
|
+
@title = INDEX_TITLE # used to indicate 'selected' in the menu
|
96
|
+
@file_path = 'index.html'
|
97
|
+
@link_path = '/'
|
98
|
+
@posts_hash = posts_hash
|
99
|
+
@tags_hash = tags_hash
|
100
|
+
@num_posts = num_posts
|
101
|
+
@recent_posts = recent_posts
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_html(pages)
|
105
|
+
years = @posts_hash.keys
|
106
|
+
Index_view.new(:doc => self, :pages => pages, :recent_posts => @recent_posts,
|
107
|
+
:years => years, :title_tag => SITE_TITLE, :tags_hash => @tags_hash,
|
108
|
+
:num_posts => @num_posts).to_html(:prettyprint => PRETTY_HTML)
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_atom
|
112
|
+
feed = Atom::Feed.new do |f|
|
113
|
+
f.title = SITE_TITLE
|
114
|
+
f.links << Atom::Link.new(:href => SITE_URI)
|
115
|
+
f.updated = Time.now
|
116
|
+
f.authors << Atom::Person.new(:name => SITE_AUTHOR)
|
117
|
+
f.id = "urn:uuid:#{UUID.sha1_create(UUID_URL_NAMESPACE, SITE_URI).to_s}"
|
118
|
+
@recent_posts.each do |post|
|
119
|
+
f.entries << Atom::Entry.new do |e|
|
120
|
+
e.title = post.title
|
121
|
+
e.links << Atom::Link.new(:href => SITE_URI + post.link_path)
|
122
|
+
e.id = post.id
|
123
|
+
e.updated = post.time
|
124
|
+
e.summary = post.summary
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
feed.to_xml
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def recent_posts
|
134
|
+
recent = []
|
135
|
+
@posts_hash.each do |year,year_posts|
|
136
|
+
year_posts.sort{|a,b| b.first <=> a.first}.each do |month,month_posts|
|
137
|
+
month_posts.sort!{|a,b| b.time <=> a.time}.each do |post|
|
138
|
+
recent << post
|
139
|
+
return recent if recent.length >= NUM_RECENT_POSTS
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
return recent # if < NUM_RECENT_POSTS
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class Year_index
|
148
|
+
|
149
|
+
def initialize(year, month_hash)
|
150
|
+
@year = year
|
151
|
+
@month_hash = month_hash
|
152
|
+
end
|
153
|
+
|
154
|
+
def to_html(pages)
|
155
|
+
title_tag = SITE_TITLE + SEPARATOR + ("#{@year} Archives")
|
156
|
+
# :doc is only passed here for the menu selector.
|
157
|
+
# There's definitely a better way to do this.
|
158
|
+
Year_index_view.new(:doc => self, :year => @year, :months_hash => @month_hash,
|
159
|
+
:pages => pages, :title_tag => title_tag
|
160
|
+
).to_html(:prettyprint => PRETTY_HTML)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class Month_index
|
165
|
+
|
166
|
+
def initialize(year, month, month_posts)
|
167
|
+
@year = year
|
168
|
+
@month = month
|
169
|
+
@month_posts = month_posts
|
170
|
+
end
|
171
|
+
|
172
|
+
def to_html(pages)
|
173
|
+
title_tag = SITE_TITLE + SEPARATOR + @year.to_s + SEPARATOR + Date::MONTHNAMES[@month]
|
174
|
+
Month_index_view.new(:doc => self, :year => @year, :month => @month,
|
175
|
+
:posts => @month_posts, :pages => pages,
|
176
|
+
:title_tag => title_tag
|
177
|
+
).to_html(:prettyprint => PRETTY_HTML)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class Tag_index
|
182
|
+
|
183
|
+
include UUIDTools
|
184
|
+
|
185
|
+
def initialize(tag_set, posts)
|
186
|
+
@tag,@escaped_tag = tag_set
|
187
|
+
@posts = posts
|
188
|
+
end
|
189
|
+
|
190
|
+
def to_html(pages)
|
191
|
+
title_tag = SITE_TITLE + SEPARATOR + "tag: #{@tag}"
|
192
|
+
Tag_index_view.new(:doc => self, :tag => @tag, :escaped_tag => @escaped_tag,
|
193
|
+
:posts => @posts, :pages => pages, :title_tag => title_tag
|
194
|
+
).to_html(:prettyprint => PRETTY_HTML)
|
195
|
+
end
|
196
|
+
|
197
|
+
def to_atom
|
198
|
+
feed = Atom::Feed.new do |f|
|
199
|
+
f.title = SITE_TITLE
|
200
|
+
f.links << Atom::Link.new(:href => SITE_URI)
|
201
|
+
f.updated = Time.now
|
202
|
+
f.authors << Atom::Person.new(:name => SITE_AUTHOR)
|
203
|
+
f.id = "urn:uuid:" +
|
204
|
+
UUID.sha1_create(UUID_URL_NAMESPACE,
|
205
|
+
SITE_URI + "tags/#{@escaped_tag}").to_s
|
206
|
+
@posts.each do |post|
|
207
|
+
f.entries << Atom::Entry.new do |e|
|
208
|
+
e.title = post.title
|
209
|
+
e.links << Atom::Link.new(:href => SITE_URI + post.link_path)
|
210
|
+
e.id = post.id
|
211
|
+
e.updated = post.time
|
212
|
+
e.summary = post.summary
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
feed.to_xml
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
class All_tags_index
|
221
|
+
|
222
|
+
def initialize(tags_hash)
|
223
|
+
@tags_hash = tags_hash
|
224
|
+
end
|
225
|
+
|
226
|
+
def to_html(pages)
|
227
|
+
title_tag = SITE_TITLE + SEPARATOR + "All tags"
|
228
|
+
All_tags_index_view.new(:doc => self, :tags_hash => @tags_hash,
|
229
|
+
:pages => pages, :title_tag => title_tag
|
230
|
+
).to_html(:prettyprint => PRETTY_HTML)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Writecast
|
2
|
+
class All_tags_index_view < Base_view
|
3
|
+
|
4
|
+
def render_breadcrumb
|
5
|
+
p SEPARATOR + "all tags"
|
6
|
+
end
|
7
|
+
|
8
|
+
def render_content_inner
|
9
|
+
div :id => 'tags_list' do
|
10
|
+
ul do
|
11
|
+
@tags_hash.each do |tag_set,posts|
|
12
|
+
tag,escaped_tag = tag_set
|
13
|
+
li do
|
14
|
+
p do
|
15
|
+
a tag, :href => "/tags/#{escaped_tag}"
|
16
|
+
text " - #{posts.size} posts"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'erector'
|
2
|
+
require 'kramdown'
|
3
|
+
|
4
|
+
module Writecast
|
5
|
+
class Base_view < Erector::Widget
|
6
|
+
|
7
|
+
def content
|
8
|
+
rawtext '<!DOCTYPE HTML>'
|
9
|
+
html do
|
10
|
+
head {render_head}
|
11
|
+
body do
|
12
|
+
div(:id => 'container') do
|
13
|
+
render_body_top_inner
|
14
|
+
div(:id => 'content') {render_content_inner}
|
15
|
+
render_footer
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def render_head
|
22
|
+
render_feed_link if defined? render_feed_link
|
23
|
+
css "/static/style.css"
|
24
|
+
css "/static/coderay.css"
|
25
|
+
script :src => "/static/jquery.min.js", :type => "text/javascript"
|
26
|
+
script :src => "/static/jquery.form.js", :type => "text/javascript"
|
27
|
+
script :src => "/static/jquery.validate.js", :type => "text/javascript"
|
28
|
+
script :src => "/static/comment.js", :type => "text/javascript"
|
29
|
+
title @title_tag
|
30
|
+
end
|
31
|
+
|
32
|
+
def render_body_top_inner
|
33
|
+
div(:id => 'header') {p SITE_TITLE}
|
34
|
+
div(:id => 'menu') {render_menu}
|
35
|
+
div(:id => 'breadcrumb') {render_breadcrumb}
|
36
|
+
end
|
37
|
+
|
38
|
+
def render_footer
|
39
|
+
div :id => 'footer' do
|
40
|
+
p a 'valid html5', :href => 'http://validator.w3.org/check/referer'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def render_menu
|
45
|
+
ul :id => 'tabnav' do
|
46
|
+
@pages.each do |page|
|
47
|
+
block = ->{a page.title, :href => page.link_path}
|
48
|
+
if ((@doc.is_a?(Page) || @doc.is_a?(Index)) && page.title == @doc.title) ||
|
49
|
+
((@doc.is_a?(Post) || @doc.is_a?(Month_index) ||
|
50
|
+
@doc.is_a?(Year_index) || @doc.is_a?(Tag_index) ||
|
51
|
+
@doc.is_a?(All_tags_index)) && page.title == INDEX_TITLE)
|
52
|
+
li :id => 'selected', &block
|
53
|
+
else
|
54
|
+
li &block
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# render a summary of a single post (as a li)
|
61
|
+
def render_post_summary(post)
|
62
|
+
li do
|
63
|
+
p do
|
64
|
+
a post.title, :href => post.link_path
|
65
|
+
br
|
66
|
+
text "last modified: #{post.time}"
|
67
|
+
end
|
68
|
+
rawtext Kramdown::Document.new(post.summary, KRAMDOWN_OPTS).to_html
|
69
|
+
p {a "more...", :href => post.link_path}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# specific views subclassed from Base_view
|
76
|
+
|
77
|
+
# try to load from the override dir first
|
78
|
+
def Writecast::loadviews
|
79
|
+
def loadview view
|
80
|
+
view_dir = File.join(Dir.pwd, 'views')
|
81
|
+
begin
|
82
|
+
require File.join(view_dir, view)
|
83
|
+
rescue LoadError
|
84
|
+
require_relative view
|
85
|
+
end
|
86
|
+
end
|
87
|
+
views = 'index','post','page','month_index','year_index','tag_index','all_tags_index'
|
88
|
+
views.each {|view| loadview view}
|
89
|
+
end
|
90
|
+
Writecast::loadviews
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Writecast
|
2
|
+
class Index_view < Base_view
|
3
|
+
|
4
|
+
def render_feed_link
|
5
|
+
link :rel => 'alternate', :type => 'application/rss+xml', :href=> '/feed.atom'
|
6
|
+
end
|
7
|
+
|
8
|
+
def render_breadcrumb
|
9
|
+
p SEPARATOR
|
10
|
+
end
|
11
|
+
|
12
|
+
def render_content_inner
|
13
|
+
div :id => 'sidebar' do
|
14
|
+
div :id => 'tags' do
|
15
|
+
p do
|
16
|
+
text "tag cloud ("
|
17
|
+
a 'view list', :href => '/tags'
|
18
|
+
text ")"
|
19
|
+
end
|
20
|
+
p do
|
21
|
+
@tags_hash.each do |tag_set,posts|
|
22
|
+
tag,escaped_tag = tag_set
|
23
|
+
size = 200*(posts.size.to_f/@num_posts)
|
24
|
+
span :style => "font-size: #{50 + size}%;" do
|
25
|
+
a tag, :href => "/tags/#{escaped_tag}"
|
26
|
+
text ' '
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
div :id => 'archive' do
|
32
|
+
p 'post archive'
|
33
|
+
ul do
|
34
|
+
@years.each do |year|
|
35
|
+
li do
|
36
|
+
a year, :href => "/#{year}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
div :id => 'recent_posts' do
|
43
|
+
ul do
|
44
|
+
@recent_posts.each do |post|
|
45
|
+
render_post_summary(post) # defined in base
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Writecast
|
2
|
+
class Month_index_view < Base_view
|
3
|
+
|
4
|
+
def render_breadcrumb
|
5
|
+
p do
|
6
|
+
text SEPARATOR
|
7
|
+
a @year, :href => "/#{@year}"
|
8
|
+
text SEPARATOR + "%02d" % @month
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def render_content_inner
|
13
|
+
div :id => 'post_summaries' do
|
14
|
+
ul do
|
15
|
+
@posts.each do |post|
|
16
|
+
render_post_summary(post)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Writecast
|
2
|
+
class Page_view < Base_view
|
3
|
+
|
4
|
+
def render_breadcrumb
|
5
|
+
p text SEPARATOR + @doc.title
|
6
|
+
end
|
7
|
+
|
8
|
+
def render_content_inner
|
9
|
+
div(:id => 'meta') {p {text "last modified: #{@doc.time}"}}
|
10
|
+
div :id => 'summary' do
|
11
|
+
rawtext Kramdown::Document.new(@doc.summary, KRAMDOWN_OPTS).to_html
|
12
|
+
end
|
13
|
+
div :id => 'body' do
|
14
|
+
rawtext Kramdown::Document.new(@doc.body, KRAMDOWN_OPTS).to_html
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Writecast
|
2
|
+
class Post_view < Base_view
|
3
|
+
|
4
|
+
def render_breadcrumb
|
5
|
+
day,month,year = @doc.time.day,@doc.time.month,@doc.time.year
|
6
|
+
p do
|
7
|
+
text SEPARATOR
|
8
|
+
a year, :href => "/#{year}"
|
9
|
+
text SEPARATOR
|
10
|
+
a "%02d" % month, :href => "/#{year}/#{"%02d" % month}"
|
11
|
+
text SEPARATOR + "#{day}" + SEPARATOR + @doc.title
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def render_content_inner
|
16
|
+
div(:id => 'meta') {p {text "last modified: #{@doc.time}"}}
|
17
|
+
div :id => 'summary' do
|
18
|
+
rawtext Kramdown::Document.new(@doc.summary, KRAMDOWN_OPTS).to_html
|
19
|
+
end
|
20
|
+
div :id => 'body' do
|
21
|
+
rawtext Kramdown::Document.new(@doc.body, KRAMDOWN_OPTS).to_html
|
22
|
+
end
|
23
|
+
div :id => 'tags' do
|
24
|
+
p do
|
25
|
+
text 'tagged: '
|
26
|
+
@doc.tags and @doc.tags.each do |tag|
|
27
|
+
a tag, :href => "/tags/#{CGI.escape(tag)}"
|
28
|
+
text ', ' if tag != @doc.tags.last
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
div :id => 'comments' do
|
33
|
+
p 'comments:'
|
34
|
+
p a 'show comment form', {:id => 'toggle_comment_form', :href => '#'}
|
35
|
+
div :id => 'comment_form_wrapper', :style => 'display:none;' do
|
36
|
+
form :id => 'comment_form', :action => "/comment", :method => "post" do
|
37
|
+
label :id => 'name' do
|
38
|
+
text 'name'
|
39
|
+
input :type => 'text', :name => 'name', :class => 'required'
|
40
|
+
end
|
41
|
+
label :id => 'first_name' do
|
42
|
+
text 'first name'
|
43
|
+
input :type => 'text', :name => 'first_name'
|
44
|
+
end
|
45
|
+
label :id => 'email' do
|
46
|
+
text 'email'
|
47
|
+
input :type => 'text', :name => 'email', :class => 'required'
|
48
|
+
end
|
49
|
+
input :type => 'hidden', :name => 'file_path', :value => @doc.raw_file_path
|
50
|
+
br
|
51
|
+
label :id => 'comment' do
|
52
|
+
text 'comment'
|
53
|
+
textarea :name => 'comment', :class => 'required'
|
54
|
+
end
|
55
|
+
br
|
56
|
+
input :id => 'reset', :type => "reset", :value => "clear form"
|
57
|
+
input :id => 'submit', :type => "submit", :value => "send comment"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Writecast
|
2
|
+
class Tag_index_view < Base_view
|
3
|
+
|
4
|
+
def render_feed_link
|
5
|
+
link ({
|
6
|
+
:rel => 'alternate',
|
7
|
+
:type => 'application/rss+xml',
|
8
|
+
:href=> "/tags/#{@escaped_tag}/feed.atom"
|
9
|
+
})
|
10
|
+
end
|
11
|
+
|
12
|
+
def render_breadcrumb
|
13
|
+
p do
|
14
|
+
text SEPARATOR
|
15
|
+
a "tags", :href => '/tags'
|
16
|
+
text ": #{@tag}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_content_inner
|
21
|
+
div :id => 'post_summaries' do
|
22
|
+
ul {@posts.each {|post| render_post_summary(post)}}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Writecast
|
2
|
+
class Year_index_view < Base_view
|
3
|
+
|
4
|
+
def render_breadcrumb
|
5
|
+
p SEPARATOR + "%04d" % @year
|
6
|
+
end
|
7
|
+
|
8
|
+
def render_content_inner
|
9
|
+
div :id => 'months_list' do
|
10
|
+
ul do
|
11
|
+
@months_hash.each do |month,posts|
|
12
|
+
li do
|
13
|
+
a "%02d" % month, :href => "/#{"%04d" % @year}/#{"%02d" % month}"
|
14
|
+
text " (#{Date::MONTHNAMES[month]}) - #{posts.size} posts"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/writecast.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "writecast/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'writecast'
|
7
|
+
s.version = Writecast::VERSION
|
8
|
+
s.authors = ['Scott Leggett']
|
9
|
+
s.email = ['sml@internode.on.net']
|
10
|
+
s.homepage = 'https://github.com/smlx/writecast'
|
11
|
+
s.summary = %q{A small static site generator.}
|
12
|
+
s.description = %q{A small static site generator which will take a bunch of your writings and turn them into HTML.}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency 'kramdown' # markdown's successor
|
20
|
+
s.add_dependency 'erector' # generate html from ruby code
|
21
|
+
s.add_dependency 'activesupport' # suppresses annoying error message, and enables Erector::Widgets::Table
|
22
|
+
s.add_dependency 'i18n' # soft dependency of activesupport
|
23
|
+
s.add_dependency 'coderay' # syntax highlighting
|
24
|
+
s.add_dependency 'rack' # enable server
|
25
|
+
s.add_dependency 'ratom' # generate atom feeds
|
26
|
+
s.add_dependency 'uuidtools' # generate atom entry id's
|
27
|
+
|
28
|
+
s.add_development_dependency 'rspec'
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: writecast
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Scott Leggett
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-10-04 00:00:00 +08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: kramdown
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: erector
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activesupport
|
48
|
+
prerelease: false
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
type: :runtime
|
58
|
+
version_requirements: *id003
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: i18n
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
type: :runtime
|
71
|
+
version_requirements: *id004
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: coderay
|
74
|
+
prerelease: false
|
75
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
type: :runtime
|
84
|
+
version_requirements: *id005
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: rack
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
type: :runtime
|
97
|
+
version_requirements: *id006
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: ratom
|
100
|
+
prerelease: false
|
101
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
version: "0"
|
109
|
+
type: :runtime
|
110
|
+
version_requirements: *id007
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: uuidtools
|
113
|
+
prerelease: false
|
114
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
segments:
|
120
|
+
- 0
|
121
|
+
version: "0"
|
122
|
+
type: :runtime
|
123
|
+
version_requirements: *id008
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: rspec
|
126
|
+
prerelease: false
|
127
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
version: "0"
|
135
|
+
type: :development
|
136
|
+
version_requirements: *id009
|
137
|
+
description: A small static site generator which will take a bunch of your writings and turn them into HTML.
|
138
|
+
email:
|
139
|
+
- sml@internode.on.net
|
140
|
+
executables:
|
141
|
+
- writecast
|
142
|
+
extensions: []
|
143
|
+
|
144
|
+
extra_rdoc_files: []
|
145
|
+
|
146
|
+
files:
|
147
|
+
- .gitignore
|
148
|
+
- Gemfile
|
149
|
+
- Rakefile
|
150
|
+
- TODO
|
151
|
+
- bin/writecast
|
152
|
+
- lib/writecast.rb
|
153
|
+
- lib/writecast/commands/help.rb
|
154
|
+
- lib/writecast/commands/init.rb
|
155
|
+
- lib/writecast/commands/override.rb
|
156
|
+
- lib/writecast/commands/process.rb
|
157
|
+
- lib/writecast/commands/serve.rb
|
158
|
+
- lib/writecast/config.rb
|
159
|
+
- lib/writecast/models.rb
|
160
|
+
- lib/writecast/version.rb
|
161
|
+
- lib/writecast/views/all_tags_index.rb
|
162
|
+
- lib/writecast/views/base.rb
|
163
|
+
- lib/writecast/views/index.rb
|
164
|
+
- lib/writecast/views/month_index.rb
|
165
|
+
- lib/writecast/views/page.rb
|
166
|
+
- lib/writecast/views/post.rb
|
167
|
+
- lib/writecast/views/tag_index.rb
|
168
|
+
- lib/writecast/views/year_index.rb
|
169
|
+
- writecast.gemspec
|
170
|
+
has_rdoc: true
|
171
|
+
homepage: https://github.com/smlx/writecast
|
172
|
+
licenses: []
|
173
|
+
|
174
|
+
post_install_message:
|
175
|
+
rdoc_options: []
|
176
|
+
|
177
|
+
require_paths:
|
178
|
+
- lib
|
179
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
180
|
+
none: false
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
segments:
|
185
|
+
- 0
|
186
|
+
version: "0"
|
187
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
188
|
+
none: false
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
segments:
|
193
|
+
- 0
|
194
|
+
version: "0"
|
195
|
+
requirements: []
|
196
|
+
|
197
|
+
rubyforge_project:
|
198
|
+
rubygems_version: 1.3.7
|
199
|
+
signing_key:
|
200
|
+
specification_version: 3
|
201
|
+
summary: A small static site generator.
|
202
|
+
test_files: []
|
203
|
+
|