zenweb 2.18.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +426 -0
- data/Manifest.txt +54 -0
- data/README.txt +63 -0
- data/Rakefile +22 -0
- data/bin/zenweb +27 -0
- data/bin/zenwebpage +66 -0
- data/bin/zenwebsite +39 -0
- data/design/REQUIREMENTS.txt +52 -0
- data/design/ZENWEB_2.txt +69 -0
- data/design/heirarchy.png +0 -0
- data/design/heirarchy.tgif +311 -0
- data/docs/Customizing +76 -0
- data/docs/FAQ +12 -0
- data/docs/Features +128 -0
- data/docs/Presentation +88 -0
- data/docs/QuickStart +32 -0
- data/docs/Renderers +85 -0
- data/docs/SiteMap +13 -0
- data/docs/YourOwnWebsite +32 -0
- data/docs/index +14 -0
- data/docs/metadata.txt +10 -0
- data/lib/ZenWeb.rb +850 -0
- data/lib/ZenWeb/CalendarRenderer.rb +162 -0
- data/lib/ZenWeb/CompactRenderer.rb +45 -0
- data/lib/ZenWeb/CompositeRenderer.rb +63 -0
- data/lib/ZenWeb/FileAttachmentRenderer.rb +57 -0
- data/lib/ZenWeb/FooterRenderer.rb +38 -0
- data/lib/ZenWeb/GenericRenderer.rb +143 -0
- data/lib/ZenWeb/HeaderRenderer.rb +52 -0
- data/lib/ZenWeb/HtmlRenderer.rb +81 -0
- data/lib/ZenWeb/HtmlTableRenderer.rb +94 -0
- data/lib/ZenWeb/HtmlTemplateRenderer.rb +173 -0
- data/lib/ZenWeb/MetadataRenderer.rb +83 -0
- data/lib/ZenWeb/RelativeRenderer.rb +98 -0
- data/lib/ZenWeb/RubyCodeRenderer.rb +56 -0
- data/lib/ZenWeb/SitemapRenderer.rb +56 -0
- data/lib/ZenWeb/StandardRenderer.rb +40 -0
- data/lib/ZenWeb/StupidRenderer.rb +88 -0
- data/lib/ZenWeb/SubpageRenderer.rb +45 -0
- data/lib/ZenWeb/TextToHtmlRenderer.rb +219 -0
- data/lib/ZenWeb/TocRenderer.rb +61 -0
- data/lib/ZenWeb/XXXRenderer.rb +32 -0
- data/test/SiteMap +14 -0
- data/test/Something +4 -0
- data/test/include.txt +3 -0
- data/test/index +8 -0
- data/test/metadata.txt +10 -0
- data/test/ryand/SiteMap +10 -0
- data/test/ryand/blah +4 -0
- data/test/ryand/blah-blah +4 -0
- data/test/ryand/index +52 -0
- data/test/ryand/metadata.txt +2 -0
- data/test/ryand/stuff/index +4 -0
- data/test/test_zenweb.rb +1624 -0
- metadata +161 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
# this is a simple template. Globally replace Calendar with the name of
|
2
|
+
# your renderer and then go fill in YYY with the appropriate content.
|
3
|
+
|
4
|
+
require 'ZenWeb/GenericRenderer'
|
5
|
+
require 'date' # which requires rational
|
6
|
+
require 'time'
|
7
|
+
|
8
|
+
# class Integer
|
9
|
+
# alias :slowgcd :gcd
|
10
|
+
# def gcd(n)
|
11
|
+
# m = abs
|
12
|
+
# while n != 0
|
13
|
+
# m %= n
|
14
|
+
# tmp = m; m = n; n = tmp
|
15
|
+
# end
|
16
|
+
# m.abs
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
|
20
|
+
# require 'tally'
|
21
|
+
# class Integer
|
22
|
+
# tally :gcd, true
|
23
|
+
# end
|
24
|
+
|
25
|
+
=begin
|
26
|
+
|
27
|
+
= Class CalendarRenderer
|
28
|
+
|
29
|
+
DOC
|
30
|
+
|
31
|
+
=== Methods
|
32
|
+
|
33
|
+
=end
|
34
|
+
|
35
|
+
class CalendarRenderer < GenericRenderer
|
36
|
+
|
37
|
+
DAYS_IN_MONTH = [
|
38
|
+
[nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
39
|
+
[nil, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
40
|
+
]
|
41
|
+
|
42
|
+
=begin
|
43
|
+
|
44
|
+
--- CalendarRenderer#render(content)
|
45
|
+
|
46
|
+
DOC
|
47
|
+
|
48
|
+
=end
|
49
|
+
|
50
|
+
def render(content)
|
51
|
+
events = Hash.new { |h,k| h[k] = [] }
|
52
|
+
reverse = false
|
53
|
+
self.scan_region(content, /<cal/i, /<\/cal>/i) do |line, context|
|
54
|
+
case context
|
55
|
+
when :START then
|
56
|
+
reverse = true if line =~ /<cal\s+reverse\s*>/i
|
57
|
+
when :END then
|
58
|
+
self.generate_calendars(events, reverse)
|
59
|
+
else
|
60
|
+
if line =~ /(\d\d\d\d-\d\d-\d\d):\s*(.*)/ then
|
61
|
+
description = $2
|
62
|
+
time = Time.parse($1) # NOT Date! hellishly slow!
|
63
|
+
events[$1] << description
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
return self.result
|
69
|
+
end
|
70
|
+
|
71
|
+
def generate_calendar(year, month, events)
|
72
|
+
|
73
|
+
current_events = []
|
74
|
+
|
75
|
+
which_year = Date.leap?(year) ? 1 : 0
|
76
|
+
date_start = Date.civil(year, month, 1)
|
77
|
+
last_day = DAYS_IN_MONTH[which_year][month]
|
78
|
+
date_end = Date.civil(year, month, -1)
|
79
|
+
|
80
|
+
push "<table class=\"calendar\">"
|
81
|
+
push "<tr>"
|
82
|
+
push "<td valign=\"top\">" # calendar
|
83
|
+
|
84
|
+
m2 = "%02d" % month
|
85
|
+
push "<table class=\"view y#{year} m#{m2}\">\n"
|
86
|
+
|
87
|
+
long_month_name = Date::MONTHNAMES[month]
|
88
|
+
month_name = Date::ABBR_MONTHNAMES[month].downcase
|
89
|
+
|
90
|
+
push "<tr class=\"title\">\n"
|
91
|
+
push "<th colspan=7>#{long_month_name} #{year}</th>\n"
|
92
|
+
push "</tr>\n"
|
93
|
+
|
94
|
+
push "<tr class=\"weektitle\"\n"
|
95
|
+
push Date::ABBR_DAYNAMES.map { |d| "<th class=\"#{d.downcase}\">#{d}</th>" }.join("\n")
|
96
|
+
push "</tr>\n"
|
97
|
+
|
98
|
+
dow_start = date_start.wday
|
99
|
+
push "<tr class=\"days firstweek\">\n"
|
100
|
+
push "<td colspan=#{dow_start}> </td>\n" unless dow_start == 0
|
101
|
+
|
102
|
+
last_sunday = date_end.day - date_end.wday + 1
|
103
|
+
|
104
|
+
week = 1
|
105
|
+
wday = dow_start
|
106
|
+
|
107
|
+
day_last = date_end.day
|
108
|
+
|
109
|
+
1.upto(day_last) do |day|
|
110
|
+
|
111
|
+
event=""
|
112
|
+
cal = Time.local(year, month, day).strftime("%Y-%m-%d")
|
113
|
+
if events.has_key? cal then
|
114
|
+
current_events << "<li>#{cal}:\n<ul>\n"
|
115
|
+
events[cal].each do |description|
|
116
|
+
current_events << "<li>#{description}\n"
|
117
|
+
end
|
118
|
+
current_events << "</ul>\n"
|
119
|
+
event=" event"
|
120
|
+
end
|
121
|
+
|
122
|
+
dow = Date::ABBR_DAYNAMES[(day + dow_start - 1) % 7].downcase
|
123
|
+
d2 = "%02d" % day
|
124
|
+
push "<td class=\"d#{d2} #{dow}#{event}\">#{day}</td>\n"
|
125
|
+
|
126
|
+
if day != day_last and wday == 6 then
|
127
|
+
push "</tr>\n"
|
128
|
+
unless day == last_sunday then
|
129
|
+
push "<tr class=\"days\">\n"
|
130
|
+
else
|
131
|
+
push "<tr class=\"days lastweek\">\n"
|
132
|
+
end
|
133
|
+
week += 1
|
134
|
+
end
|
135
|
+
|
136
|
+
wday += 1
|
137
|
+
wday = 0 if wday >= 7 # remember 0..6
|
138
|
+
end
|
139
|
+
|
140
|
+
day_count = 7-date_end.wday-1
|
141
|
+
push "<td colspan=#{day_count}> </td>\n" unless day_count == 0
|
142
|
+
push "</tr>\n"
|
143
|
+
push "</table>\n"
|
144
|
+
push "</td>\n" # /calendar
|
145
|
+
push "<td class=\"eventlist\">\n" # events
|
146
|
+
push "<ul>\n"
|
147
|
+
push current_events.join('')
|
148
|
+
push "</ul>\n"
|
149
|
+
push "</td>\n" # /events
|
150
|
+
push "</tr>\n"
|
151
|
+
push "</table>\n"
|
152
|
+
end
|
153
|
+
|
154
|
+
def generate_calendars(events, should_reverse=false)
|
155
|
+
active_months = events.keys.map { |d| d[0..6] }.sort.uniq
|
156
|
+
active_months = active_months.reverse if should_reverse
|
157
|
+
active_months.each do |ym|
|
158
|
+
year, month = ym.split(/-/).map { |n| n.to_i }
|
159
|
+
self.generate_calendar(year, month, events)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
|
4
|
+
require 'ZenWeb/GenericRenderer'
|
5
|
+
|
6
|
+
=begin
|
7
|
+
|
8
|
+
= Class CompactRenderer
|
9
|
+
|
10
|
+
CompactRenderer compacts HTML whitespace and comments to minimize how much text gets served.
|
11
|
+
|
12
|
+
=== Methods
|
13
|
+
|
14
|
+
=end
|
15
|
+
|
16
|
+
class CompactRenderer < GenericRenderer
|
17
|
+
|
18
|
+
=begin
|
19
|
+
|
20
|
+
--- CompactRenderer#render(content)
|
21
|
+
|
22
|
+
Compacts HTML blah blah DOC
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
def render(content)
|
27
|
+
|
28
|
+
# protect pre blocks
|
29
|
+
content = content.gsub(/(<pre>)([\s\S]*?)(<\/pre>)/i) do
|
30
|
+
p1 = $1
|
31
|
+
body = $2
|
32
|
+
p2 = $3
|
33
|
+
body = body.gsub(/\n/, 1.chr).gsub(/ /, 2.chr)
|
34
|
+
p1 + body + p2
|
35
|
+
end
|
36
|
+
|
37
|
+
content = content.gsub(/\n\r/, '').gsub(/\s+/, ' ').gsub(/> </, '><').strip
|
38
|
+
content = content.gsub(1.chr, "\n").gsub(2.chr, " ")
|
39
|
+
|
40
|
+
push(content)
|
41
|
+
|
42
|
+
return self.result
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'ZenWeb/GenericRenderer'
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
= Class CompositeRenderer
|
6
|
+
|
7
|
+
Allows multiple renderers to be plugged into a single renderer.
|
8
|
+
|
9
|
+
=== Methods
|
10
|
+
|
11
|
+
=end
|
12
|
+
|
13
|
+
class CompositeRenderer < GenericRenderer
|
14
|
+
|
15
|
+
attr_reader :renderers
|
16
|
+
|
17
|
+
=begin
|
18
|
+
|
19
|
+
--- CompositeRenderer#new(document)
|
20
|
+
|
21
|
+
Creates a new CompositeRenderer.
|
22
|
+
|
23
|
+
=end
|
24
|
+
|
25
|
+
def initialize(document)
|
26
|
+
super(document)
|
27
|
+
@renderers = []
|
28
|
+
end
|
29
|
+
|
30
|
+
=begin
|
31
|
+
|
32
|
+
--- CompositeRenderer#render(content)
|
33
|
+
|
34
|
+
Renders by running all of the renderers in sequence.
|
35
|
+
|
36
|
+
=end
|
37
|
+
|
38
|
+
def render(content)
|
39
|
+
@renderers.each { | renderer |
|
40
|
+
content = renderer.render(content)
|
41
|
+
}
|
42
|
+
return content
|
43
|
+
end
|
44
|
+
|
45
|
+
=begin
|
46
|
+
|
47
|
+
--- CompositeRenderer#addRenderer(renderer)
|
48
|
+
|
49
|
+
Adds renderer to the list of renderers used by this composite.
|
50
|
+
|
51
|
+
=end
|
52
|
+
|
53
|
+
def addRenderer(renderer)
|
54
|
+
|
55
|
+
if (renderer.nil? or ! renderer.kind_of? GenericRenderer) then
|
56
|
+
raise ArgumentError, "You may only add an instance of GenericRenderer"
|
57
|
+
end
|
58
|
+
|
59
|
+
@renderers.push(renderer)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# this is a simple template. Globally replace FileAttachment with the name of
|
2
|
+
# your renderer and then go fill in YYY with the appropriate content.
|
3
|
+
|
4
|
+
require 'ZenWeb/GenericRenderer'
|
5
|
+
|
6
|
+
=begin
|
7
|
+
|
8
|
+
= Class FileAttachmentRenderer
|
9
|
+
|
10
|
+
Finds content between <file name="name">...</file> tags. Writes the
|
11
|
+
content to a file of the given name and includes a link to the file.
|
12
|
+
|
13
|
+
=== Methods
|
14
|
+
|
15
|
+
=end
|
16
|
+
|
17
|
+
class FileAttachmentRenderer < GenericRenderer
|
18
|
+
|
19
|
+
=begin
|
20
|
+
|
21
|
+
--- FileAttachmentRenderer#render(content)
|
22
|
+
|
23
|
+
YYY
|
24
|
+
|
25
|
+
=end
|
26
|
+
|
27
|
+
def render(content)
|
28
|
+
|
29
|
+
start_re = /<file\s+name\s*=\s*\"([\w\.-]+)\"\s*>/i
|
30
|
+
end_re = /<\/file>/i
|
31
|
+
|
32
|
+
file_content = []
|
33
|
+
name = nil
|
34
|
+
self.scan_region(content, start_re, end_re) do |line, context|
|
35
|
+
case context
|
36
|
+
when :START then
|
37
|
+
name = $1 if line =~ /name\s*=\s*\"([\w\.-]+)\"/i
|
38
|
+
when :END then
|
39
|
+
raise "name is undefined, add name= attribute" if name.nil?
|
40
|
+
dir = File.dirname @document.htmlpath
|
41
|
+
path = File.join(dir, name)
|
42
|
+
push "\n"
|
43
|
+
push "<A HREF=\"#{name}\">Download #{name}</A>\n"
|
44
|
+
File.open(path, "w") do |file|
|
45
|
+
file.print file_content.join('')
|
46
|
+
end
|
47
|
+
file_content = []
|
48
|
+
else
|
49
|
+
file_content.push line
|
50
|
+
push " " + line
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
return self.result.strip
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'ZenWeb/GenericRenderer'
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
= Class FooterRenderer
|
6
|
+
|
7
|
+
Inserts a footer based on metadata.
|
8
|
+
|
9
|
+
=== Methods
|
10
|
+
|
11
|
+
=end
|
12
|
+
|
13
|
+
class FooterRenderer < GenericRenderer
|
14
|
+
|
15
|
+
=begin
|
16
|
+
|
17
|
+
--- FooterRenderer#render(content)
|
18
|
+
|
19
|
+
Adds a footer if the ((|footer|)) metadata item exists. If the
|
20
|
+
document contains a BODY close HTML tag, then the footer
|
21
|
+
immediately precedes it, otherwise it is simply at the bottom.
|
22
|
+
|
23
|
+
=end
|
24
|
+
|
25
|
+
def render(content)
|
26
|
+
|
27
|
+
footer = @document['footer'] || nil
|
28
|
+
|
29
|
+
if footer then
|
30
|
+
content.sub!(/(<\/BODY>|\z)/i) {
|
31
|
+
footer + $1
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
return content
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,143 @@
|
|
1
|
+
$TESTING = FALSE unless defined? $TESTING
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
= Class GenericRenderer
|
6
|
+
|
7
|
+
A GenericRenderer provides an interface for all renderers. It renders
|
8
|
+
nothing itself.
|
9
|
+
|
10
|
+
=== Methods
|
11
|
+
|
12
|
+
=end
|
13
|
+
|
14
|
+
class GenericRenderer
|
15
|
+
|
16
|
+
=begin
|
17
|
+
|
18
|
+
--- GenericRenderer.new(document)
|
19
|
+
|
20
|
+
Instantiates a generic renderer with a reference to
|
21
|
+
((|document|)), it\'s website and sitemap.
|
22
|
+
|
23
|
+
=end
|
24
|
+
|
25
|
+
# REFACTOR: do not take a document at all
|
26
|
+
def initialize(document)
|
27
|
+
@document = document
|
28
|
+
@website = @document.website
|
29
|
+
@sitemap = @website.sitemap
|
30
|
+
@result = []
|
31
|
+
end
|
32
|
+
|
33
|
+
=begin
|
34
|
+
|
35
|
+
--- GenericRenderer#push(obj)
|
36
|
+
|
37
|
+
Pushes a string representation of ((|obj|)) onto the result
|
38
|
+
array. If ((|obj|)) is an array, it iterates each item and pushes
|
39
|
+
them (recursively). If it is not an array, it pushes (({obj.to_s})).
|
40
|
+
|
41
|
+
=end
|
42
|
+
|
43
|
+
def push(obj)
|
44
|
+
if obj.is_a?(Array) then
|
45
|
+
stuff = obj.flatten
|
46
|
+
@result.push(*stuff) unless stuff.empty?
|
47
|
+
else
|
48
|
+
if false then
|
49
|
+
@result.push(obj.to_s)
|
50
|
+
else
|
51
|
+
@result.push(obj)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
=begin
|
57
|
+
|
58
|
+
--- GenericRenderer#unshift(obj)
|
59
|
+
|
60
|
+
Same as ((<GenericRenderer#push>)) but prepends instead of appends.
|
61
|
+
|
62
|
+
=end
|
63
|
+
|
64
|
+
def unshift(obj)
|
65
|
+
if obj.is_a?(Array) then
|
66
|
+
obj.reverse.each { | item |
|
67
|
+
self.unshift(item)
|
68
|
+
}
|
69
|
+
else
|
70
|
+
@result.unshift(obj.to_s)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
# DOC GenericRenderer#result
|
76
|
+
def result(clear=true)
|
77
|
+
result = @result.join('')
|
78
|
+
@result = [] if clear
|
79
|
+
return result
|
80
|
+
end
|
81
|
+
|
82
|
+
=begin
|
83
|
+
|
84
|
+
--- GenericRenderer#render(content)
|
85
|
+
|
86
|
+
Renders the content. Does nothing in GenericRenderer, but is
|
87
|
+
expected to be overridden by subclasses. ((|content|)) is an array
|
88
|
+
of strings and render must return an array of strings.
|
89
|
+
|
90
|
+
NEW: the argument and result are now a single string!
|
91
|
+
|
92
|
+
=end
|
93
|
+
|
94
|
+
# REFACTOR: pass in content and document to render
|
95
|
+
def render(content)
|
96
|
+
return content
|
97
|
+
end
|
98
|
+
|
99
|
+
def each_paragraph(content)
|
100
|
+
content.scan(/.+?(?:#{$/}#{$/}+|\Z)/m) do |block|
|
101
|
+
$stderr.puts "BLOCK = #{block.inspect}" if $DEBUG
|
102
|
+
yield(block)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def each_paragraph_matching(content, pattern)
|
107
|
+
$stderr.puts "CONTENT = #{content.inspect}" if $DEBUG
|
108
|
+
self.each_paragraph(content) do |block|
|
109
|
+
$stderr.puts "PARAGRAPH = #{block.inspect}" if $DEBUG
|
110
|
+
if block =~ pattern then
|
111
|
+
yield(block)
|
112
|
+
else
|
113
|
+
push block
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def scan_region(content, region_start, region_end)
|
119
|
+
matching = false
|
120
|
+
content.scan(/.*#{$/}?/) do |l|
|
121
|
+
# TODO: detect region_end w/o start and freak
|
122
|
+
# TODO: detect nesting and freak
|
123
|
+
if l =~ region_start then
|
124
|
+
matching = true
|
125
|
+
$stderr.puts :START, l.inspect if $DEBUG
|
126
|
+
yield(l, :START)
|
127
|
+
matching = false if l =~ region_end
|
128
|
+
elsif l =~ region_end then
|
129
|
+
matching = false
|
130
|
+
$stderr.puts :END, l.inspect if $DEBUG
|
131
|
+
yield(l, :END)
|
132
|
+
elsif matching then
|
133
|
+
$stderr.puts :MIDDLE, l.inspect if $DEBUG
|
134
|
+
yield(l, :MIDDLE)
|
135
|
+
else
|
136
|
+
$stderr.puts :IGNORED, l.inspect if $DEBUG
|
137
|
+
push l
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|