clayoven 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/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: []
|