clayoven 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/clayoven +24 -0
- data/lib/clayoven.rb +106 -0
- data/lib/clayoven/claytext.rb +80 -0
- data/lib/clayoven/config.rb +24 -0
- data/lib/clayoven/httpd.rb +24 -0
- data/lib/clayoven/imapd.rb +46 -0
- metadata +68 -0
data/bin/clayoven
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w{ .. lib })
|
4
|
+
|
5
|
+
require 'clayoven'
|
6
|
+
|
7
|
+
case ARGV[0]
|
8
|
+
when "httpd"
|
9
|
+
Httpd.start
|
10
|
+
when "imapd"
|
11
|
+
while 1
|
12
|
+
mails = Imapd.poll
|
13
|
+
if not mails.empty?
|
14
|
+
Core.main
|
15
|
+
mails.each { |mail|
|
16
|
+
`git add .`
|
17
|
+
puts `git commit -a -m "#{mail.filename}: new post\n\n#{mail.date}\n#{mail.msgid}"`
|
18
|
+
}
|
19
|
+
end
|
20
|
+
sleep 1800
|
21
|
+
end
|
22
|
+
else
|
23
|
+
Core.main
|
24
|
+
end
|
data/lib/clayoven.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'slim'
|
4
|
+
require 'clayoven/config'
|
5
|
+
require 'clayoven/claytext'
|
6
|
+
require 'clayoven/httpd'
|
7
|
+
require 'clayoven/imapd'
|
8
|
+
|
9
|
+
def when_introduced(filename)
|
10
|
+
timestamp = `git log --reverse --pretty="%at" #{filename} 2>/dev/null | head -n 1`.strip
|
11
|
+
if timestamp == ""
|
12
|
+
Time.now
|
13
|
+
else
|
14
|
+
Time.at(timestamp.to_i)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Core
|
19
|
+
class Page
|
20
|
+
attr_accessor :filename, :permalink, :timestamp, :title, :topic, :body,
|
21
|
+
:paragraphs, :target, :indexfill, :topics
|
22
|
+
|
23
|
+
def render(topics)
|
24
|
+
@topics = topics
|
25
|
+
@paragraphs = ClayText.process! @body
|
26
|
+
Slim::Engine.set_default_options pretty: true, sort_attrs: false
|
27
|
+
rendered = Slim::Template.new { IO.read("design/template.slim") }.render(self)
|
28
|
+
File.open(@target, mode="w") { |targetio|
|
29
|
+
nbytes = targetio.write(rendered)
|
30
|
+
puts "[GEN] #{@target} (#{nbytes} bytes out)"
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class IndexPage < Page
|
36
|
+
def initialize(filename)
|
37
|
+
@filename = filename
|
38
|
+
if @filename == "index"
|
39
|
+
@permalink = @filename
|
40
|
+
else
|
41
|
+
@permalink = filename.split(".index")[0]
|
42
|
+
end
|
43
|
+
@topic = @permalink
|
44
|
+
@target = "#{@permalink}.html"
|
45
|
+
@timestamp = when_introduced @filename
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class ContentPage < Page
|
50
|
+
attr_accessor :pub_date
|
51
|
+
|
52
|
+
def initialize(filename)
|
53
|
+
@filename = filename
|
54
|
+
@topic, @permalink = @filename.split(":", 2)
|
55
|
+
@target = "#{@permalink}.html"
|
56
|
+
@indexfill = nil
|
57
|
+
@timestamp = when_introduced @filename
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.main
|
62
|
+
if not File.exists? "index"
|
63
|
+
puts "error: index file not found; aborting"
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
|
67
|
+
config = ConfigData.new
|
68
|
+
all_files = (Dir.entries(".") -
|
69
|
+
[".", "..", ".clayoven", "design"]).reject { |entry|
|
70
|
+
config.ignore.any? { |pattern| %r{#{pattern}} =~ entry }
|
71
|
+
}
|
72
|
+
|
73
|
+
if not Dir.entries("design").include? "template.slim"
|
74
|
+
puts "error: design/template.slim file not found; aborting"
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
|
78
|
+
index_files = ["index"] + all_files.select { |file| /\.index$/ =~ file }
|
79
|
+
content_files = all_files - index_files
|
80
|
+
topics = index_files.map { |file| file.split(".index")[0] }.uniq
|
81
|
+
|
82
|
+
# Next, look for stray files
|
83
|
+
(content_files.reject { |file| topics.include? (file.split(":", 2)[0]) })
|
84
|
+
.each { |stray_entry|
|
85
|
+
content_files = content_files - [stray_entry]
|
86
|
+
puts "warning: #{stray_entry} is a stray file or directory; ignored"
|
87
|
+
}
|
88
|
+
|
89
|
+
index_pages = index_files.map { |filename| IndexPage.new(filename) }
|
90
|
+
content_pages = content_files.map { |filename| ContentPage.new(filename) }
|
91
|
+
|
92
|
+
# First, fill in all the page attributes
|
93
|
+
(index_pages + content_pages).each { |page|
|
94
|
+
page.title, page.body = (IO.read page.filename).split("\n\n", 2)
|
95
|
+
}
|
96
|
+
|
97
|
+
# Compute the indexfill for indexes
|
98
|
+
topics.each { |topic|
|
99
|
+
topic_index = index_pages.select { |page| page.topic == topic }[0]
|
100
|
+
topic_index.indexfill = content_pages.select { |page|
|
101
|
+
page.topic == topic }.sort { |a, b| b.timestamp <=> a.timestamp }
|
102
|
+
}
|
103
|
+
|
104
|
+
(index_pages + content_pages).each { |page| page.render topics }
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class Paragraph
|
2
|
+
attr_accessor :content, :first, :type
|
3
|
+
|
4
|
+
def initialize(content)
|
5
|
+
@content = content
|
6
|
+
@first = false
|
7
|
+
@type = :plain
|
8
|
+
end
|
9
|
+
|
10
|
+
def is_first?
|
11
|
+
@first
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClayText
|
16
|
+
def self.mark_emailquote!(paragraph)
|
17
|
+
paragraph.type = :emailquote
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.mark_codeblock!(paragraph)
|
21
|
+
paragraph.type = :codeblock
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.anchor_footerlinks!(paragraph)
|
25
|
+
paragraph.content.gsub!(%r{^(\[\d+\]:) (.*://(.*))}) {
|
26
|
+
"#{$1} <a href=\"#{$2}\">#{$3[0, 64]}#{%{...} if $3.length > 67}</a>"
|
27
|
+
}
|
28
|
+
paragraph.type = :footer
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.process!(body)
|
32
|
+
htmlescape_rules = {
|
33
|
+
"&" => "&",
|
34
|
+
"\"" => """,
|
35
|
+
"'" => "'",
|
36
|
+
"<" => "<",
|
37
|
+
">" => ">"
|
38
|
+
}.freeze
|
39
|
+
|
40
|
+
paragraph_types = [:plain, :emailquote, :codeblock, :header, :footer]
|
41
|
+
paragraph_rules = {
|
42
|
+
Proc.new { |line| line.start_with? "> " } => method(:mark_emailquote!),
|
43
|
+
Proc.new { |line| line.start_with? " " } => method(:mark_codeblock!),
|
44
|
+
Proc.new { |line| /^\[\d+\]: / =~ line } => method(:anchor_footerlinks!)
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
# First, htmlescape the body text
|
48
|
+
body.gsub!(/[&"'<>]/, htmlescape_rules)
|
49
|
+
|
50
|
+
paragraphs = []
|
51
|
+
body.split("\n\n").each { |content|
|
52
|
+
paragraphs << Paragraph.new(content)
|
53
|
+
}
|
54
|
+
|
55
|
+
paragraphs[0].first = true
|
56
|
+
if paragraphs[0].content.start_with? "(" and
|
57
|
+
paragraphs[0].content.end_with? ")"
|
58
|
+
paragraphs[0].type = :header
|
59
|
+
end
|
60
|
+
|
61
|
+
# Paragraph-level processing
|
62
|
+
paragraphs.each { |paragraph|
|
63
|
+
paragraph_rules.each { |proc_match, callback|
|
64
|
+
if paragraph.content.lines.all? &proc_match
|
65
|
+
callback.call paragraph
|
66
|
+
end
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
# Generate is_*? methods for Paragraph
|
71
|
+
Paragraph.class_eval {
|
72
|
+
paragraph_types.each { |type|
|
73
|
+
define_method("is_#{type.to_s}?") { @type == type }
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
body = paragraphs.map(&:content).join("\n\n")
|
78
|
+
paragraphs
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class ConfigData
|
4
|
+
attr_accessor :rootpath, :rcpath, :ignorepath, :rc, :ignore
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@rootpath = ".clayoven"
|
8
|
+
@rcpath = File.expand_path "~/.clayovenrc"
|
9
|
+
@ignorepath = "#{rootpath}/ignore"
|
10
|
+
@ignore = ["\\.html$", "~$", "^.\#", "^\#.*\#$",
|
11
|
+
"^\\.git$", "^\\.gitignore$", "^\\.htaccess$"]
|
12
|
+
@rc = nil
|
13
|
+
|
14
|
+
Dir.mkdir @rootpath if not Dir.exists? @rootpath
|
15
|
+
if File.exists? @ignorepath
|
16
|
+
@ignore = IO.read(@ignorepath).split("\n")
|
17
|
+
else
|
18
|
+
File.open(@ignorepath, "w") { |ignoreio|
|
19
|
+
ignoreio.write @ignore.join("\n") }
|
20
|
+
puts "[NOTE] #{@ignorepath} populated with sane defaults"
|
21
|
+
end
|
22
|
+
@rc = YAML.load_file @rcpath if File.exists? @rcpath
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
|
3
|
+
module Httpd
|
4
|
+
def self.start
|
5
|
+
port = 8000
|
6
|
+
callback = Proc.new { |req, res|
|
7
|
+
if %r{^/$} =~ req.path_info
|
8
|
+
res.set_redirect WEBrick::HTTPStatus::Found, "index.html"
|
9
|
+
end
|
10
|
+
if %r{^/([^.]*)$} =~ req.path_info
|
11
|
+
res.set_redirect WEBrick::HTTPStatus::Found, "#{$1}.html"
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
server = WEBrick::HTTPServer.new(:Port => port,
|
16
|
+
:RequestCallback => callback,
|
17
|
+
:DocumentRoot => Dir.pwd)
|
18
|
+
|
19
|
+
puts "clayoven serving at: http://localhost:#{port}"
|
20
|
+
|
21
|
+
trap(:INT) { server.shutdown }
|
22
|
+
server.start
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'net/imap'
|
2
|
+
require_relative 'config'
|
3
|
+
|
4
|
+
class Mail
|
5
|
+
attr_accessor :filename, :date, :msgid
|
6
|
+
|
7
|
+
def initialize(filename, date, msgid)
|
8
|
+
@filename = filename
|
9
|
+
@date = date
|
10
|
+
@msgid = msgid
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Imapd
|
15
|
+
def self.poll
|
16
|
+
config = ConfigData.new
|
17
|
+
if not config.rc
|
18
|
+
puts "error: #{config.rcpath} not found; aborting"
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
mails = []
|
22
|
+
server = Net::IMAP.new(config.rc["server"],
|
23
|
+
{:port => config.rc["port"], :ssl => config.rc["ssl"]})
|
24
|
+
trap(:INT) { exit 1 }
|
25
|
+
server.login config.rc["username"], config.rc["password"]
|
26
|
+
puts "[NOTE] LOGIN successful"
|
27
|
+
server.examine "INBOX"
|
28
|
+
server.search(["ALL"]).each { |id|
|
29
|
+
message = server.fetch(id, ["ENVELOPE", "RFC822.TEXT"])[0]
|
30
|
+
if message.attr["ENVELOPE"].sender[0].mailbox == "artagnon" and
|
31
|
+
message.attr["ENVELOPE"].sender[0].host == "gmail.com" and
|
32
|
+
message.attr["ENVELOPE"].sender[0].name == "Ramkumar Ramachandra"
|
33
|
+
date = message.attr["ENVELOPE"].date
|
34
|
+
msgid = message.attr["ENVELOPE"].message_id
|
35
|
+
title, filename = message.attr["ENVELOPE"].subject.split(" # ")
|
36
|
+
next if File.exists? filename
|
37
|
+
File.open(filename, "w") { |targetio|
|
38
|
+
targetio.write([title, message.attr["RFC822.TEXT"].delete("\r")].join "\n\n")
|
39
|
+
}
|
40
|
+
mails << Mail.new(filename, date, msgid)
|
41
|
+
end
|
42
|
+
}
|
43
|
+
server.disconnect
|
44
|
+
mails
|
45
|
+
end
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: clayoven
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ramkumar Ramachandra
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: slim
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.3.6
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.3.6
|
30
|
+
description: clayoven is a modern website generator with a traditional design
|
31
|
+
email: artagnon@gmail.com
|
32
|
+
executables:
|
33
|
+
- clayoven
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- lib/clayoven.rb
|
38
|
+
- lib/clayoven/config.rb
|
39
|
+
- lib/clayoven/claytext.rb
|
40
|
+
- lib/clayoven/httpd.rb
|
41
|
+
- lib/clayoven/imapd.rb
|
42
|
+
- bin/clayoven
|
43
|
+
homepage: https://github.com/artagnon/clayoven
|
44
|
+
licenses:
|
45
|
+
- MIT
|
46
|
+
post_install_message: clayoven installed! Run `clayoven` or `clayoven (httpd|imapd)`
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.9.2
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project: clayoven
|
64
|
+
rubygems_version: 1.8.23
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Modern website generator with a traditional design
|
68
|
+
test_files: []
|