jekyll 0.3.0 → 0.4.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.
Potentially problematic release.
This version of jekyll might be problematic. Click here for more details.
- data/History.txt +25 -0
- data/README.textile +93 -23
- data/VERSION.yml +4 -0
- data/bin/jekyll +9 -0
- data/lib/jekyll.rb +7 -3
- data/lib/jekyll/converters/mephisto.rb +56 -1
- data/lib/jekyll/converters/mt.rb +1 -1
- data/lib/jekyll/converters/textpattern.rb +50 -0
- data/lib/jekyll/converters/typo.rb +49 -0
- data/lib/jekyll/converters/wordpress.rb +9 -8
- data/lib/jekyll/convertible.rb +14 -3
- data/lib/jekyll/filters.rb +18 -3
- data/lib/jekyll/post.rb +9 -5
- data/lib/jekyll/site.rb +45 -28
- data/lib/jekyll/tags/highlight.rb +20 -4
- data/lib/jekyll/tags/include.rb +6 -2
- data/test/dest/2008/10/18/foo-bar.html +28 -0
- data/test/dest/2008/11/21/complex.html +29 -0
- data/test/dest/2008/12/13/include.html +30 -0
- data/test/dest/_posts/2008-10-18-foo-bar.html +28 -0
- data/test/dest/_posts/2008-11-21-complex.html +29 -0
- data/test/dest/_posts/2008-12-03-permalinked-post.html +2 -0
- data/test/dest/_posts/2008-12-13-include.html +30 -0
- data/test/dest/category/2008/09/23/categories.html +27 -0
- data/test/dest/category/_posts/2008-9-23-categories.html +27 -0
- data/test/dest/css/screen.css +76 -0
- data/test/dest/foo/2008/12/12/topical-post.html +28 -0
- data/test/dest/foo/_posts/bar/2008-12-12-topical-post.html +28 -0
- data/test/dest/index.html +60 -0
- data/test/dest/my_category/permalinked-post +2 -0
- data/test/dest/z_category/2008/09/23/categories.html +27 -0
- data/test/dest/z_category/_posts/2008-9-23-categories.html +27 -0
- data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
- data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
- data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
- data/test/test_filters.rb +37 -0
- data/test/test_generated_site.rb +1 -0
- data/test/test_post.rb +15 -1
- data/test/test_site.rb +5 -2
- data/test/test_tags.rb +31 -0
- metadata +85 -37
- data/Manifest.txt +0 -36
- data/Rakefile +0 -24
- data/jekyll.gemspec +0 -51
data/lib/jekyll/convertible.rb
CHANGED
@@ -24,16 +24,26 @@ module Jekyll
|
|
24
24
|
#
|
25
25
|
# Returns nothing
|
26
26
|
def transform
|
27
|
-
case
|
28
|
-
when
|
27
|
+
case Jekyll.content_type
|
28
|
+
when :textile
|
29
29
|
self.ext = ".html"
|
30
30
|
self.content = RedCloth.new(self.content).to_html
|
31
|
-
when
|
31
|
+
when :markdown
|
32
32
|
self.ext = ".html"
|
33
33
|
self.content = Jekyll.markdown_proc.call(self.content)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
def determine_content_type
|
38
|
+
case self.ext[1..-1]
|
39
|
+
when /textile/i
|
40
|
+
return :textile
|
41
|
+
when /markdown/i, /mkdn/i, /md/i
|
42
|
+
return :markdown
|
43
|
+
end
|
44
|
+
return :unknown
|
45
|
+
end
|
46
|
+
|
37
47
|
# Add any necessary layouts to this convertible document
|
38
48
|
# +layouts+ is a Hash of {"name" => "layout"}
|
39
49
|
# +site_payload+ is the site payload hash
|
@@ -41,6 +51,7 @@ module Jekyll
|
|
41
51
|
# Returns nothing
|
42
52
|
def do_layout(payload, layouts)
|
43
53
|
# render and transform content (this becomes the final content of the object)
|
54
|
+
Jekyll.content_type = self.determine_content_type
|
44
55
|
self.content = Liquid::Template.parse(self.content).render(payload, [Jekyll::Filters])
|
45
56
|
self.transform
|
46
57
|
|
data/lib/jekyll/filters.rb
CHANGED
@@ -14,11 +14,26 @@ module Jekyll
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def xml_escape(input)
|
17
|
-
input.gsub("<", "<").gsub(">", ">")
|
17
|
+
input.gsub("&", "&").gsub("<", "<").gsub(">", ">")
|
18
18
|
end
|
19
19
|
|
20
20
|
def number_of_words(input)
|
21
21
|
input.split.length
|
22
|
-
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def array_to_sentence_string(array)
|
25
|
+
connector = "and"
|
26
|
+
case array.length
|
27
|
+
when 0
|
28
|
+
""
|
29
|
+
when 1
|
30
|
+
array[0].to_s
|
31
|
+
when 2
|
32
|
+
"#{array[0]} #{connector} #{array[1]}"
|
33
|
+
else
|
34
|
+
"#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
23
38
|
end
|
24
|
-
end
|
39
|
+
end
|
data/lib/jekyll/post.rb
CHANGED
@@ -61,15 +61,19 @@ module Jekyll
|
|
61
61
|
# The generated directory into which the post will be placed
|
62
62
|
# upon generation. This is derived from the permalink or, if
|
63
63
|
# permalink is absent, set to the default date
|
64
|
-
# e.g. "/2008/11/05/"
|
64
|
+
# e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing
|
65
65
|
#
|
66
66
|
# Returns <String>
|
67
67
|
def dir
|
68
68
|
if permalink
|
69
|
-
permalink.to_s.split("/")[0..-2].join("/")
|
69
|
+
permalink.to_s.split("/")[0..-2].join("/") + '/'
|
70
70
|
else
|
71
71
|
prefix = self.categories.empty? ? '' : '/' + self.categories.join('/')
|
72
|
-
|
72
|
+
if Jekyll.permalink_style == :date
|
73
|
+
prefix + date.strftime("/%Y/%m/%d/")
|
74
|
+
else
|
75
|
+
prefix + '/'
|
76
|
+
end
|
73
77
|
end
|
74
78
|
end
|
75
79
|
|
@@ -87,7 +91,7 @@ module Jekyll
|
|
87
91
|
#
|
88
92
|
# Returns <String>
|
89
93
|
def url
|
90
|
-
self.dir + self.slug + ".html"
|
94
|
+
permalink || self.dir + self.slug + ".html"
|
91
95
|
end
|
92
96
|
|
93
97
|
# The UID for this post (useful in feeds)
|
@@ -154,7 +158,7 @@ module Jekyll
|
|
154
158
|
#
|
155
159
|
# Returns <Hash>
|
156
160
|
def to_liquid
|
157
|
-
{ "title" => self.data["title"] ||
|
161
|
+
{ "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
|
158
162
|
"url" => self.url,
|
159
163
|
"date" => self.date,
|
160
164
|
"id" => self.id,
|
data/lib/jekyll/site.rb
CHANGED
@@ -2,7 +2,7 @@ module Jekyll
|
|
2
2
|
|
3
3
|
class Site
|
4
4
|
attr_accessor :source, :dest
|
5
|
-
attr_accessor :layouts, :posts
|
5
|
+
attr_accessor :layouts, :posts, :categories
|
6
6
|
|
7
7
|
# Initialize the site
|
8
8
|
# +source+ is String path to the source directory containing
|
@@ -16,6 +16,7 @@ module Jekyll
|
|
16
16
|
self.dest = dest
|
17
17
|
self.layouts = {}
|
18
18
|
self.posts = []
|
19
|
+
self.categories = Hash.new { |hash, key| hash[key] = Array.new }
|
19
20
|
end
|
20
21
|
|
21
22
|
# Do the actual work of processing the site and generating the
|
@@ -63,6 +64,7 @@ module Jekyll
|
|
63
64
|
if Post.valid?(f)
|
64
65
|
post = Post.new(self.source, dir, f)
|
65
66
|
self.posts << post
|
67
|
+
post.categories.each { |c| self.categories[c] << post }
|
66
68
|
end
|
67
69
|
end
|
68
70
|
|
@@ -72,6 +74,7 @@ module Jekyll
|
|
72
74
|
end
|
73
75
|
|
74
76
|
self.posts.sort!
|
77
|
+
self.categories.values.map { |cats| cats.sort! { |a, b| b <=> a} }
|
75
78
|
rescue Errno::ENOENT => e
|
76
79
|
# ignore missing layout dir
|
77
80
|
end
|
@@ -88,7 +91,8 @@ module Jekyll
|
|
88
91
|
# Copy all regular files from <source> to <dest>/ ignoring
|
89
92
|
# any files/directories that are hidden or backup files (start
|
90
93
|
# with "." or end with "~") or contain site content (start with "_")
|
91
|
-
# unless they are "_posts" directories
|
94
|
+
# unless they are "_posts" directories or web server files such as
|
95
|
+
# '.htaccess'
|
92
96
|
# The +dir+ String is a relative path used to call this method
|
93
97
|
# recursively as it descends through directories
|
94
98
|
#
|
@@ -98,8 +102,10 @@ module Jekyll
|
|
98
102
|
entries = Dir.entries(base)
|
99
103
|
entries = entries.reject { |e| e[-1..-1] == '~' }
|
100
104
|
entries = entries.reject do |e|
|
101
|
-
(e != '_posts') and ['.', '_'].include?(e[0..0])
|
105
|
+
(e != '_posts') and ['.', '_'].include?(e[0..0]) unless ['.htaccess'].include?(e)
|
102
106
|
end
|
107
|
+
directories = entries.select { |e| File.directory?(File.join(base, e)) }
|
108
|
+
files = entries.reject { |e| File.directory?(File.join(base, e)) }
|
103
109
|
|
104
110
|
# we need to make sure to process _posts *first* otherwise they
|
105
111
|
# might not be available yet to other templates as {{ site.posts }}
|
@@ -107,42 +113,53 @@ module Jekyll
|
|
107
113
|
entries.delete('_posts')
|
108
114
|
read_posts(dir)
|
109
115
|
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
else
|
116
|
-
first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
|
117
|
-
|
118
|
-
if first3 == "---"
|
119
|
-
# file appears to have a YAML header so process it as a page
|
120
|
-
page = Page.new(self.source, dir, f)
|
121
|
-
page.render(self.layouts, site_payload)
|
122
|
-
page.write(self.dest)
|
116
|
+
[directories, files].each do |entries|
|
117
|
+
entries.each do |f|
|
118
|
+
if File.directory?(File.join(base, f))
|
119
|
+
next if self.dest.sub(/\/$/, '') == File.join(base, f)
|
120
|
+
transform_pages(File.join(dir, f))
|
123
121
|
else
|
124
|
-
|
125
|
-
|
126
|
-
|
122
|
+
first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
|
123
|
+
|
124
|
+
if first3 == "---"
|
125
|
+
# file appears to have a YAML header so process it as a page
|
126
|
+
page = Page.new(self.source, dir, f)
|
127
|
+
page.render(self.layouts, site_payload)
|
128
|
+
page.write(self.dest)
|
129
|
+
else
|
130
|
+
# otherwise copy the file without transforming it
|
131
|
+
FileUtils.mkdir_p(File.join(self.dest, dir))
|
132
|
+
FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
|
133
|
+
end
|
127
134
|
end
|
128
135
|
end
|
129
136
|
end
|
130
137
|
end
|
131
|
-
|
138
|
+
|
139
|
+
# Constructs a hash map of Posts indexed by the specified Post attribute
|
140
|
+
#
|
141
|
+
# Returns {post_attr => [<Post>]}
|
142
|
+
def post_attr_hash(post_attr)
|
143
|
+
# Build a hash map based on the specified post attribute ( post attr => array of posts )
|
144
|
+
# then sort each array in reverse order
|
145
|
+
hash = Hash.new { |hash, key| hash[key] = Array.new }
|
146
|
+
self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
|
147
|
+
hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
|
148
|
+
return hash
|
149
|
+
end
|
150
|
+
|
132
151
|
# The Hash payload containing site-wide data
|
133
152
|
#
|
134
|
-
# Returns {"site" => {"time" => <Time>,
|
153
|
+
# Returns {"site" => {"time" => <Time>,
|
154
|
+
# "posts" => [<Post>],
|
155
|
+
# "categories" => [<Post>],
|
156
|
+
# "topics" => [<Post>] }}
|
135
157
|
def site_payload
|
136
|
-
# Build the category hash map of category ( names => arrays of posts )
|
137
|
-
# then sort each array in reverse order
|
138
|
-
categories = Hash.new { |hash, key| hash[key] = Array.new }
|
139
|
-
self.posts.each { |p| p.categories.each { |c| categories[c] << p } }
|
140
|
-
categories.values.map { |cats| cats.sort! { |a, b| b <=> a} }
|
141
|
-
|
142
158
|
{"site" => {
|
143
159
|
"time" => Time.now,
|
144
160
|
"posts" => self.posts.sort { |a,b| b <=> a },
|
145
|
-
"categories" => categories
|
161
|
+
"categories" => post_attr_hash('categories'),
|
162
|
+
"topics" => post_attr_hash('topics')
|
146
163
|
}}
|
147
164
|
end
|
148
165
|
end
|
@@ -2,10 +2,22 @@ module Jekyll
|
|
2
2
|
|
3
3
|
class HighlightBlock < Liquid::Block
|
4
4
|
include Liquid::StandardFilters
|
5
|
+
# we need a language, but the linenos argument is optional.
|
6
|
+
SYNTAX = /(\w+)\s?(:?linenos)?\s?/
|
5
7
|
|
6
|
-
def initialize(tag_name,
|
8
|
+
def initialize(tag_name, markup, tokens)
|
7
9
|
super
|
8
|
-
|
10
|
+
if markup =~ SYNTAX
|
11
|
+
@lang = $1
|
12
|
+
if defined? $2
|
13
|
+
# additional options to pass to Albino.
|
14
|
+
@options = { 'O' => 'linenos=inline' }
|
15
|
+
else
|
16
|
+
@options = {}
|
17
|
+
end
|
18
|
+
else
|
19
|
+
raise SyntaxError.new("Syntax Error in 'highlight' - Valid syntax: highlight <lang> [linenos]")
|
20
|
+
end
|
9
21
|
end
|
10
22
|
|
11
23
|
def render(context)
|
@@ -17,7 +29,11 @@ module Jekyll
|
|
17
29
|
end
|
18
30
|
|
19
31
|
def render_pygments(context, code)
|
20
|
-
|
32
|
+
if Jekyll.content_type == :markdown
|
33
|
+
return "\n" + Albino.new(code, @lang).to_s(@options) + "\n"
|
34
|
+
else
|
35
|
+
"<notextile>" + Albino.new(code, @lang).to_s(@options) + "</notextile>"
|
36
|
+
end
|
21
37
|
end
|
22
38
|
|
23
39
|
def render_codehighlighter(context, code)
|
@@ -34,4 +50,4 @@ module Jekyll
|
|
34
50
|
|
35
51
|
end
|
36
52
|
|
37
|
-
Liquid::Template.register_tag('highlight', Jekyll::HighlightBlock)
|
53
|
+
Liquid::Template.register_tag('highlight', Jekyll::HighlightBlock)
|
data/lib/jekyll/tags/include.rb
CHANGED
@@ -14,7 +14,11 @@ module Jekyll
|
|
14
14
|
Dir.chdir(File.join(Jekyll.source, '_includes')) do
|
15
15
|
choices = Dir['**/*'].reject { |x| File.symlink?(x) }
|
16
16
|
if choices.include?(@file)
|
17
|
-
File.read(@file)
|
17
|
+
source = File.read(@file)
|
18
|
+
partial = Liquid::Template.parse(source)
|
19
|
+
context.stack do
|
20
|
+
partial.render(context)
|
21
|
+
end
|
18
22
|
else
|
19
23
|
"Included file '#{@file}' not found in _includes directory"
|
20
24
|
end
|
@@ -24,4 +28,4 @@ module Jekyll
|
|
24
28
|
|
25
29
|
end
|
26
30
|
|
27
|
-
Liquid::Template.register_tag('include', Jekyll::IncludeTag)
|
31
|
+
Liquid::Template.register_tag('include', Jekyll::IncludeTag)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
7
|
+
<title>Foo Bar</title>
|
8
|
+
<meta name="author" content="<%= @page.author %>" />
|
9
|
+
|
10
|
+
<!-- CodeRay syntax highlighting CSS -->
|
11
|
+
<link rel="stylesheet" href="/css/coderay.css" type="text/css" />
|
12
|
+
|
13
|
+
<!-- Homepage CSS -->
|
14
|
+
<link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen, projection" />
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
|
18
|
+
<div class="site">
|
19
|
+
<div class="title">
|
20
|
+
Tom Preston-Werner
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<h1>Foo Bar</h1>
|
24
|
+
<p>Best <strong>post</strong> ever</p>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
</body>
|
28
|
+
</html>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
7
|
+
<title>Complex</title>
|
8
|
+
<meta name="author" content="<%= @page.author %>" />
|
9
|
+
|
10
|
+
<!-- CodeRay syntax highlighting CSS -->
|
11
|
+
<link rel="stylesheet" href="/css/coderay.css" type="text/css" />
|
12
|
+
|
13
|
+
<!-- Homepage CSS -->
|
14
|
+
<link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen, projection" />
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
|
18
|
+
<div class="site">
|
19
|
+
<div class="title">
|
20
|
+
Tom Preston-Werner
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<p>url: /2008/11/21/complex.html<br />
|
24
|
+
date: Fri Nov 21 00:00:00 -0800 2008<br />
|
25
|
+
id: /2008/11/21/complex</p>
|
26
|
+
</div>
|
27
|
+
|
28
|
+
</body>
|
29
|
+
</html>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
7
|
+
<title>Include</title>
|
8
|
+
<meta name="author" content="<%= @page.author %>" />
|
9
|
+
|
10
|
+
<!-- CodeRay syntax highlighting CSS -->
|
11
|
+
<link rel="stylesheet" href="/css/coderay.css" type="text/css" />
|
12
|
+
|
13
|
+
<!-- Homepage CSS -->
|
14
|
+
<link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen, projection" />
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
|
18
|
+
<div class="site">
|
19
|
+
<div class="title">
|
20
|
+
Tom Preston-Werner
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<hr />
|
24
|
+
<p>Tom Preston-Werner github.com/mojombo</p>
|
25
|
+
|
26
|
+
<p>This <em>is</em> cool</p>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
</body>
|
30
|
+
</html>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
7
|
+
<title>Foo Bar</title>
|
8
|
+
<meta name="author" content="<%= @page.author %>" />
|
9
|
+
|
10
|
+
<!-- CodeRay syntax highlighting CSS -->
|
11
|
+
<link rel="stylesheet" href="/css/coderay.css" type="text/css" />
|
12
|
+
|
13
|
+
<!-- Homepage CSS -->
|
14
|
+
<link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen, projection" />
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
|
18
|
+
<div class="site">
|
19
|
+
<div class="title">
|
20
|
+
Tom Preston-Werner
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<h1>Foo Bar</h1>
|
24
|
+
<p>Best <strong>post</strong> ever</p>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
</body>
|
28
|
+
</html>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
7
|
+
<title>Complex</title>
|
8
|
+
<meta name="author" content="<%= @page.author %>" />
|
9
|
+
|
10
|
+
<!-- CodeRay syntax highlighting CSS -->
|
11
|
+
<link rel="stylesheet" href="/css/coderay.css" type="text/css" />
|
12
|
+
|
13
|
+
<!-- Homepage CSS -->
|
14
|
+
<link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen, projection" />
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
|
18
|
+
<div class="site">
|
19
|
+
<div class="title">
|
20
|
+
Tom Preston-Werner
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<p>url: <br />
|
24
|
+
date: <br />
|
25
|
+
id:</p>
|
26
|
+
</div>
|
27
|
+
|
28
|
+
</body>
|
29
|
+
</html>
|