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.
@@ -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
+