writecast 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ .*.swp
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
6
+ bundle/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in writecast.gemspec
4
+ gemspec
@@ -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?
@@ -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
@@ -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,3 @@
1
+ module Writecast
2
+ VERSION = "0.0.1"
3
+ 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
@@ -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
+