git.fiddlerwoaroof.com
Browse code

initial commit

fiddlerwoaroof authored on 14/07/2014 20:00:44
Showing 20 changed files
2 2
new file mode 120000
... ...
@@ -0,0 +1 @@
1
+main.py
0 2
\ No newline at end of file
1 3
new file mode 100644
... ...
@@ -0,0 +1,195 @@
1
+import url_handler
2
+import feedparser
3
+import hashlib
4
+import json
5
+#import redis
6
+
7
+class Feed(object):
8
+	@property
9
+	def url(self):
10
+		return self._url
11
+	@url.setter
12
+	def url(self, url):
13
+		self.urlhash = hashlib.md5(url.encode('utf-8')).hexdigest()
14
+		self._url = url
15
+
16
+	def __init__(self, title, link, url, etag=None, modified=None, status=0):
17
+		self.urlhash = hashlib.md5(url.encode('utf-8')).hexdigest()
18
+		self.title, self.link, self.url = title, link, url
19
+		print('__init__', 'etag is', etag, 'modified is', modified)
20
+		self.etag = etag
21
+		self.modified = modified
22
+		self.status = int(status)
23
+		self.entries = []
24
+	def add_entry(self, entry):
25
+		if self.entries:
26
+			self.entries[-1].sep = False
27
+		self.entries.append(entry)
28
+		self.entries[-1].sep = True
29
+
30
+	def to_redis(self, redis):
31
+		feed_key = self.get_feed_key(self.url)
32
+		object_prefix = '%s:%%s' % feed_key
33
+		print(object_prefix)
34
+		redis.set(feed_key, 'exists')
35
+		redis.set(object_prefix % 'title', self.title)
36
+		redis.set(object_prefix % 'link', self.link)
37
+		redis.set(object_prefix % 'url', self.url)
38
+		redis.set(object_prefix % 'etag', self.etag)
39
+		redis.set(object_prefix % 'modified', self.modified)
40
+		redis.set(object_prefix % 'status', self.status)
41
+		self.put_entries(redis, object_prefix)
42
+
43
+	def put_entries(self, redis, object_prefix):
44
+		for entry in reversed(self.entries):
45
+			eid = hashlib.md5(entry.id.encode('utf-8')).hexdigest()
46
+			entry_key = object_prefix % ('entry:%s' % eid)
47
+			redis.sadd(object_prefix % 'entries', eid)
48
+			entry.to_redis(redis, entry_key)
49
+
50
+	@classmethod
51
+	def get_feed_key(cls, url):
52
+		return 'feed:%s' % hashlib.md5(url.encode('utf-8')).hexdigest()
53
+
54
+	@classmethod
55
+	def from_redis(cls, redis, url):
56
+		feed_key = cls.get_feed_key(url)
57
+		if redis.get(feed_key):
58
+			object_prefix = '%s:%%s' % feed_key
59
+			title = redis.get(object_prefix % 'title').decode('utf-8')
60
+			link = redis.get(object_prefix % 'link').decode('utf-8')
61
+			url = redis.get(object_prefix % 'url').decode('utf-8')
62
+			etag = redis.get(object_prefix % 'etag').decode('utf-8')
63
+			modified = redis.get(object_prefix % 'modified').decode('utf-8')
64
+			status = redis.get(object_prefix % 'status').decode('utf-8')
65
+			self = cls(title, link, url)
66
+			entries = redis.smembers(object_prefix % 'entries')
67
+			for eid in entries:
68
+				eid = eid.decode('utf-8')
69
+				entry_key = object_prefix % ('entry:%s' % eid)
70
+				hl = Headline.from_redis(redis, entry_key)
71
+				if hl is None: break
72
+				else: self.add_entry(hl)
73
+
74
+			if self.entries != []:
75
+				self.entries[-1].sep = False
76
+				self.entries.sort(key=lambda x:x.date)
77
+				self.entries[-1].sep = True
78
+			return self
79
+
80
+	@classmethod
81
+	def pull_feed(cls, url, etag=None, modified=None):
82
+		print('etag is', etag, 'modified is', modified)
83
+		feed = url_handler.URLHandler.handle(url, etag=etag, modified=modified)
84
+		return cls.from_parsed_feed(feed, url)
85
+
86
+	@classmethod
87
+	def from_parsed_feed(cls, data, url):
88
+		title = data.feed.title
89
+		url = url
90
+		link = data.feed.link
91
+		etag = data.etag if hasattr(data, 'etag') else 'No Etag'
92
+		modified = data.modified if hasattr(data, 'modified') else 'No Last Modified'
93
+		print('parsed_feed', 'etag is', etag, 'modified is', modified)
94
+		status = data.status
95
+
96
+		self = cls(title, link, url, etag=etag, modified=modified, status=status)
97
+
98
+		for entry in data.entries:
99
+			hl = Headline(entry.title, entry.link, date=entry.published_parsed, id=entry.id)
100
+			self.add_entry(hl)
101
+		return self
102
+
103
+	@classmethod
104
+	def get_feed(cls, url, redis=None):
105
+		res = None
106
+		update = False
107
+		newfeed = None
108
+
109
+		if redis is not None:
110
+			res = cls.from_redis(redis, url)
111
+			if res is not None:
112
+				newfeed = url_handler.URLHandler.handle(url, etag=res.etag, modified=res.modified)
113
+				update = newfeed.status != 304
114
+				print('newfeed.status is', newfeed.status, 'update is', update)
115
+
116
+		print('res is', res, 'update is', update, 'url is', url)
117
+		if update or res is None:
118
+			if update:
119
+				updates = cls.from_parsed_feed(newfeed, url)
120
+				object_prefix = '%s:%%s' % cls.get_feed_key(url)
121
+				updates.put_entries(redis, object_prefix)
122
+				print('putting updates!')
123
+				updates.to_redis(redis)
124
+				res = cls.from_redis(redis, url)
125
+			else:
126
+				data = url_handler.URLHandler.handle(url)
127
+				res = cls.from_parsed_feed(data, url)
128
+				res.to_redis(redis)
129
+		return res
130
+
131
+
132
+class Headline(object):
133
+	@property
134
+	def url(self):
135
+		return self._url
136
+	@url.setter
137
+	def url(self, url):
138
+		self.urlhash = hashlib.md5(url.encode('utf-8')).hexdigest()
139
+		self._url = url
140
+
141
+	serialized_attributes = ['title', 'url', 'img', 'id', 'date']
142
+	def __init__(self, title, url, sep=False, img=None, id=None, date=None):
143
+		for x in self.serialized_attributes:
144
+			setattr(self, x, locals()[x])
145
+		self.date = list(self.date)
146
+		self.sep = sep
147
+
148
+	def __repr__(self):
149
+		return '<%s>' % ', '.join(str(getattr(self,x)) for x in self.serialized_attributes)
150
+	trans_map = dict(date=json.dumps)
151
+	rtrans_map = { y:x for (x,y) in trans_map.items() }
152
+	def to_redis(self, redis, entry_key):
153
+		redis.set(entry_key, 'exists')
154
+		object_prefix = '%s:%%s' % entry_key
155
+		for x in self.serialized_attributes:
156
+			redis.set(object_prefix % x, self.trans_map.get(x, lambda x:x)(getattr(self, x)))
157
+
158
+	@classmethod
159
+	def from_redis(cls, redis, entry_key):
160
+		if redis.get(entry_key) is not None:
161
+			object_prefix = '%s:%%s' % entry_key
162
+			args = {}
163
+			for x in cls.serialized_attributes:
164
+				args[x] = redis.get(object_prefix % x).decode('utf-8')
165
+				args[x] = cls.rtrans_map.get(x, lambda x:x)(args[x])
166
+			return cls(**args)
167
+
168
+	@classmethod
169
+	def from_rss(cls, entry):
170
+		name_mapping = dict(
171
+			url='link',
172
+			date='published_parsed',
173
+		)
174
+		self = cls(entry.title, entry.link, id=entry.id, date=entry.published_parsed)
175
+
176
+	def to_json(self):
177
+		return json.dumps([self.title, self.link, self.sep, self.img])
178
+	@classmethod
179
+	def from_json(cls, enc):
180
+		enc = json.loads(enc)
181
+		self = cls(*json.loads(enc))
182
+
183
+class Feeds(object):
184
+	def __init__(self, urls, redis=None):
185
+		self.feeds = list(filter(None, (Feed.get_feed(url, redis) for url in urls)))
186
+		print(self.feeds)
187
+
188
+if __name__ == '__main__':
189
+	import json
190
+	import redis
191
+	print('getting feeds . . .', end=' ')
192
+	with open('blogs.json') as f:
193
+		feeds = json.load(f)
194
+	feeds = Feeds(feeds, redis.Redis())
195
+	print('done.')
0 196
new file mode 100644
... ...
@@ -0,0 +1,15 @@
1
+[
2
+	"http://thomism.wordpress.com/feed/",
3
+	"http://www.adashofbitters.com/feed/",
4
+	"http://feeds.feedburner.com/jlongster",
5
+	"http://americandrink.net/rss",
6
+	"http://www.cocktailnerd.com/feed/",
7
+	"http://melissamccart.tumblr.com/rss",
8
+	"http://xkcd.com/rss.xml",
9
+	"http://www.reddit.com/r/roguelikes/",
10
+	"http://www.reddit.com/r/roguelikedev/",
11
+	"http://www.reddit.com/r/programming/",
12
+	"http://www.reddit.com/r/dcss/",
13
+	"http://www.reddit.com/r/nethack/",
14
+	"http://www.reddit.com/r/python/"
15
+]
0 16
new file mode 100644
... ...
@@ -0,0 +1,6 @@
1
+import jinja2
2
+import re
3
+
4
+special_re = re.compile(r'([\[\](){}+.`!\\])')
5
+def markdown_quote(s):
6
+	return ' '.join(special_re.sub(r'\\\1', s).split())
0 7
new file mode 100644
... ...
@@ -0,0 +1,101 @@
1
+import feedparser
2
+from flask import Flask, url_for, redirect, request, render_template, make_response
3
+import jinja_filters
4
+import json
5
+import logging
6
+import random
7
+
8
+app = Flask(__name__)
9
+app.template_filter('mesc')(jinja_filters.markdown_quote)
10
+
11
+def count_chars(word):
12
+    special = set('ijltf')
13
+    return sum( (0.5 if l in special else 1) for l in word )
14
+
15
+def divide_entries(entries):
16
+    def chars_to_lines(entry):
17
+        title, _, __, is_head, _ = entry
18
+        divisor = 30 if is_head else 43
19
+        char_count = len(title)
20
+        line_count = char_count / divisor
21
+        line_count = max(line_count, 1) # everything always takes at least one line
22
+        return line_count
23
+
24
+    tagged_entries = [(chars_to_lines(x), x) for x in entries] # 36: no. chars in line
25
+    total_lines = sum(x for x,_ in tagged_entries)
26
+    target_lines = total_lines / 3
27
+
28
+    #target_chars = total_chars / 3
29
+    #average_chars = total_chars // len(entries)
30
+
31
+    #target_ents = len(entries) / 3
32
+    #average_target_chars = average_chars * target_ents
33
+
34
+    #print(target_chars, average_target_chars)
35
+
36
+    #target_chars = (target_chars*2 + average_target_chars*3) // 5
37
+
38
+    #print(target_chars)
39
+
40
+    columns = [[]]
41
+    accum = 0
42
+    print()
43
+    for n, entry in tagged_entries:
44
+        accum += n
45
+        if (
46
+            (accum > target_lines and len(columns) < 3)
47
+        ):
48
+            columns.append([])
49
+            print(accum)
50
+            accum = accum - target_lines
51
+        columns[-1].append(entry)
52
+    print(accum)
53
+    return columns
54
+
55
+import redis
56
+import blog_puller
57
+def get_columns(urls):
58
+    r = redis.Redis()
59
+    feeds = blog_puller.Feeds(urls, r)
60
+    entries = []
61
+    for feed in feeds.feeds:
62
+        entries.append( (feed.title, feed.link, False, True, None) )
63
+        for entry in feed.entries:
64
+            entries.append( (entry.title, entry.url, entry.sep, False, None) )
65
+
66
+    return divide_entries(entries)
67
+
68
+
69
+def get_blogs():
70
+    with open('blogs.json') as f:
71
+        blogs = json.load(f)
72
+    columns = get_columns(blogs)
73
+    for x in columns:
74
+        a,b,_,d, e = x.pop()
75
+        x.append( (a,b,False,d, e) )
76
+    return columns
77
+
78
+@app.route("/")
79
+def main(idx=0):
80
+    return render_template('main.html', columns=get_blogs())
81
+
82
+@app.route("/<fn>.md")
83
+@app.route("/.md")
84
+def markdown(idx=0, fn=None):
85
+    return  make_response(render_template('markdown.md', columns=get_blogs()),
86
+                200, {'Content-Type': 'text/markdown; charset=utf-8'})
87
+
88
+@app.route("/toolbar/<path:url>")
89
+def toolbar(url):
90
+    return render_template('toolbar.html', url=url)
91
+
92
+@app.route("/show/<path:url>")
93
+def ope(url=None):
94
+    if url is not None:
95
+        resp = make_response(render_template('frame.html', target_site=url), 200)
96
+        resp.headers['X-Frame-Options'] = 'GOFORIT'
97
+        return resp
98
+    else:
99
+        return 'not found'
100
+
101
+app.run('172.16.1.2', debug=True)
0 102
new file mode 100644
... ...
@@ -0,0 +1,4 @@
1
+snippet class
2
+	class ${1}:
3
+		def __init__(${2}):
4
+			${3}
0 5
new file mode 100644
1 6
Binary files /dev/null and b/static/images/apple-touch-icon-114x114.png differ
2 7
new file mode 100644
3 8
Binary files /dev/null and b/static/images/apple-touch-icon-72x72.png differ
4 9
new file mode 100644
5 10
Binary files /dev/null and b/static/images/apple-touch-icon.png differ
6 11
new file mode 100644
7 12
Binary files /dev/null and b/static/images/favicon.ico differ
8 13
new file mode 100644
... ...
@@ -0,0 +1,269 @@
1
+/*
2
+* Skeleton V1.2
3
+* Copyright 2011, Dave Gamache
4
+* www.getskeleton.com
5
+* Free to use under the MIT license.
6
+* http://www.opensource.org/licenses/mit-license.php
7
+* 6/20/2012
8
+*/
9
+
10
+
11
+/* Table of Content
12
+==================================================
13
+	#Reset & Basics
14
+	#Basic Styles
15
+	#Site Styles
16
+	#Typography
17
+	#Links
18
+	#Lists
19
+	#Images
20
+	#Buttons
21
+	#Forms
22
+	#Misc */
23
+
24
+
25
+/* #Reset & Basics (Inspired by E. Meyers)
26
+================================================== */
27
+	html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
28
+		margin: 0;
29
+		padding: 0;
30
+		border: 0;
31
+		font-size: 100%;
32
+		font: inherit;
33
+		vertical-align: baseline; }
34
+	article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
35
+		display: block; }
36
+	body {
37
+		line-height: 1; }
38
+	ol, ul {
39
+		list-style: none; }
40
+	blockquote, q {
41
+		quotes: none; }
42
+	blockquote:before, blockquote:after,
43
+	q:before, q:after {
44
+		content: '';
45
+		content: none; }
46
+	table {
47
+		border-collapse: collapse;
48
+		border-spacing: 0; }
49
+
50
+
51
+/* #Basic Styles
52
+================================================== */
53
+	body {
54
+		background: #fff;
55
+		font: 14px/21px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
56
+		color: #444;
57
+		-webkit-font-smoothing: antialiased; /* Fix for webkit rendering */
58
+		-webkit-text-size-adjust: 100%;
59
+ }
60
+
61
+
62
+/* #Typography
63
+================================================== */
64
+	h1, h2, h3, h4, h5, h6 {
65
+		color: #181818;
66
+		font-family: "Georgia", "Times New Roman", serif;
67
+		font-weight: normal; }
68
+	h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; }
69
+	h1 { font-size: 46px; line-height: 50px; margin-bottom: 14px;}
70
+	h2 { font-size: 35px; line-height: 40px; margin-bottom: 10px; }
71
+	h3 { font-size: 28px; line-height: 34px; margin-bottom: 8px; }
72
+	h4 { font-size: 21px; line-height: 30px; margin-bottom: 4px; }
73
+	h5 { font-size: 17px; line-height: 24px; }
74
+	h6 { font-size: 14px; line-height: 21px; }
75
+	.subheader { color: #777; }
76
+
77
+	p { margin: 0 0 20px 0; }
78
+	p img { margin: 0; }
79
+	p.lead { font-size: 21px; line-height: 27px; color: #777;  }
80
+
81
+	em { font-style: italic; }
82
+	strong { font-weight: bold; color: #333; }
83
+	small { font-size: 80%; }
84
+
85
+/*	Blockquotes  */
86
+	blockquote, blockquote p { font-size: 17px; line-height: 24px; color: #777; font-style: italic; }
87
+	blockquote { margin: 0 0 20px; padding: 9px 20px 0 19px; border-left: 1px solid #ddd; }
88
+	blockquote cite { display: block; font-size: 12px; color: #555; }
89
+	blockquote cite:before { content: "\2014 \0020"; }
90
+	blockquote cite a, blockquote cite a:visited, blockquote cite a:visited { color: #555; }
91
+
92
+	hr { border: solid #ddd; border-width: 1px 0 0; clear: both; margin: 10px 0 30px; height: 0; }
93
+
94
+
95
+/* #Links
96
+================================================== */
97
+	a, a:visited { color: #333; text-decoration: underline; outline: 0; }
98
+	a:hover, a:focus { color: #000; }
99
+	p a, p a:visited { line-height: inherit; }
100
+
101
+
102
+/* #Lists
103
+================================================== */
104
+	ul, ol { margin-bottom: 20px; }
105
+	ul { list-style: none outside; }
106
+	ol { list-style: decimal; }
107
+	ol, ul.square, ul.circle, ul.disc { margin-left: 30px; }
108
+	ul.square { list-style: square outside; }
109
+	ul.circle { list-style: circle outside; }
110
+	ul.disc { list-style: disc outside; }
111
+	ul ul, ul ol,
112
+	ol ol, ol ul { margin: 4px 0 5px 30px; font-size: 90%;  }
113
+	ul ul li, ul ol li,
114
+	ol ol li, ol ul li { margin-bottom: 6px; }
115
+	li { line-height: 18px; margin-bottom: 12px; }
116
+	ul.large li { line-height: 21px; }
117
+	li p { line-height: 21px; }
118
+
119
+/* #Images
120
+================================================== */
121
+
122
+	img.scale-with-grid {
123
+		max-width: 100%;
124
+		height: auto; }
125
+
126
+
127
+/* #Buttons
128
+================================================== */
129
+
130
+	.button,
131
+	button,
132
+	input[type="submit"],
133
+	input[type="reset"],
134
+	input[type="button"] {
135
+		background: #eee; /* Old browsers */
136
+		background: #eee -moz-linear-gradient(top, rgba(255,255,255,.2) 0%, rgba(0,0,0,.2) 100%); /* FF3.6+ */
137
+		background: #eee -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.2)), color-stop(100%,rgba(0,0,0,.2))); /* Chrome,Safari4+ */
138
+		background: #eee -webkit-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Chrome10+,Safari5.1+ */
139
+		background: #eee -o-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Opera11.10+ */
140
+		background: #eee -ms-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* IE10+ */
141
+		background: #eee linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* W3C */
142
+	  border: 1px solid #aaa;
143
+	  border-top: 1px solid #ccc;
144
+	  border-left: 1px solid #ccc;
145
+	  -moz-border-radius: 3px;
146
+	  -webkit-border-radius: 3px;
147
+	  border-radius: 3px;
148
+	  color: #444;
149
+	  display: inline-block;
150
+	  font-size: 11px;
151
+	  font-weight: bold;
152
+	  text-decoration: none;
153
+	  text-shadow: 0 1px rgba(255, 255, 255, .75);
154
+	  cursor: pointer;
155
+	  margin-bottom: 20px;
156
+	  line-height: normal;
157
+	  padding: 8px 10px;
158
+	  font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; }
159
+
160
+	.button:hover,
161
+	button:hover,
162
+	input[type="submit"]:hover,
163
+	input[type="reset"]:hover,
164
+	input[type="button"]:hover {
165
+		color: #222;
166
+		background: #ddd; /* Old browsers */
167
+		background: #ddd -moz-linear-gradient(top, rgba(255,255,255,.3) 0%, rgba(0,0,0,.3) 100%); /* FF3.6+ */
168
+		background: #ddd -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.3)), color-stop(100%,rgba(0,0,0,.3))); /* Chrome,Safari4+ */
169
+		background: #ddd -webkit-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Chrome10+,Safari5.1+ */
170
+		background: #ddd -o-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Opera11.10+ */
171
+		background: #ddd -ms-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* IE10+ */
172
+		background: #ddd linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* W3C */
173
+	  border: 1px solid #888;
174
+	  border-top: 1px solid #aaa;
175
+	  border-left: 1px solid #aaa; }
176
+
177
+	.button:active,
178
+	button:active,
179
+	input[type="submit"]:active,
180
+	input[type="reset"]:active,
181
+	input[type="button"]:active {
182
+		border: 1px solid #666;
183
+		background: #ccc; /* Old browsers */
184
+		background: #ccc -moz-linear-gradient(top, rgba(255,255,255,.35) 0%, rgba(10,10,10,.4) 100%); /* FF3.6+ */
185
+		background: #ccc -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.35)), color-stop(100%,rgba(10,10,10,.4))); /* Chrome,Safari4+ */
186
+		background: #ccc -webkit-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Chrome10+,Safari5.1+ */
187
+		background: #ccc -o-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Opera11.10+ */
188
+		background: #ccc -ms-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* IE10+ */
189
+		background: #ccc linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* W3C */ }
190
+
191
+	.button.full-width,
192
+	button.full-width,
193
+	input[type="submit"].full-width,
194
+	input[type="reset"].full-width,
195
+	input[type="button"].full-width {
196
+		width: 100%;
197
+		padding-left: 0 !important;
198
+		padding-right: 0 !important;
199
+		text-align: center; }
200
+
201
+	/* Fix for odd Mozilla border & padding issues */
202
+	button::-moz-focus-inner,
203
+	input::-moz-focus-inner {
204
+    border: 0;
205
+    padding: 0;
206
+	}
207
+
208
+
209
+/* #Forms
210
+================================================== */
211
+
212
+	form {
213
+		margin-bottom: 20px; }
214
+	fieldset {
215
+		margin-bottom: 20px; }
216
+	input[type="text"],
217
+	input[type="password"],
218
+	input[type="email"],
219
+	textarea,
220
+	select {
221
+		border: 1px solid #ccc;
222
+		padding: 6px 4px;
223
+		outline: none;
224
+		-moz-border-radius: 2px;
225
+		-webkit-border-radius: 2px;
226
+		border-radius: 2px;
227
+		font: 13px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
228
+		color: #777;
229
+		margin: 0;
230
+		width: 210px;
231
+		max-width: 100%;
232
+		display: block;
233
+		margin-bottom: 20px;
234
+		background: #fff; }
235
+	select {
236
+		padding: 0; }
237
+	input[type="text"]:focus,
238
+	input[type="password"]:focus,
239
+	input[type="email"]:focus,
240
+	textarea:focus {
241
+		border: 1px solid #aaa;
242
+ 		color: #444;
243
+ 		-moz-box-shadow: 0 0 3px rgba(0,0,0,.2);
244
+		-webkit-box-shadow: 0 0 3px rgba(0,0,0,.2);
245
+		box-shadow:  0 0 3px rgba(0,0,0,.2); }
246
+	textarea {
247
+		min-height: 60px; }
248
+	label,
249
+	legend {
250
+		display: block;
251
+		font-weight: bold;
252
+		font-size: 13px;  }
253
+	select {
254
+		width: 220px; }
255
+	input[type="checkbox"] {
256
+		display: inline; }
257
+	label span,
258
+	legend span {
259
+		font-weight: normal;
260
+		font-size: 13px;
261
+		color: #444; }
262
+
263
+/* #Misc
264
+================================================== */
265
+	.remove-bottom { margin-bottom: 0 !important; }
266
+	.half-bottom { margin-bottom: 10px !important; }
267
+	.add-bottom { margin-bottom: 20px !important; }
268
+
269
+
0 270
new file mode 100644
... ...
@@ -0,0 +1,58 @@
1
+/*
2
+* Skeleton V1.2
3
+* Copyright 2011, Dave Gamache
4
+* www.getskeleton.com
5
+* Free to use under the MIT license.
6
+* http://www.opensource.org/licenses/mit-license.php
7
+* 6/20/2012
8
+*/
9
+
10
+/* Table of Content
11
+==================================================
12
+	#Site Styles
13
+	#Page Styles
14
+	#Media Queries
15
+	#Font-Face */
16
+
17
+/* #Site Styles
18
+================================================== */
19
+
20
+/* #Page Styles
21
+================================================== */
22
+
23
+/* #Media Queries
24
+================================================== */
25
+
26
+	/* Smaller than standard 960 (devices and browsers) */
27
+	@media only screen and (max-width: 959px) {}
28
+
29
+	/* Tablet Portrait size to standard 960 (devices and browsers) */
30
+	@media only screen and (min-width: 768px) and (max-width: 959px) {}
31
+
32
+	/* All Mobile Sizes (devices and browser) */
33
+	@media only screen and (max-width: 767px) {}
34
+
35
+	/* Mobile Landscape Size to Tablet Portrait (devices and browsers) */
36
+	@media only screen and (min-width: 480px) and (max-width: 767px) {}
37
+
38
+	/* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */
39
+	@media only screen and (max-width: 479px) {}
40
+
41
+
42
+/* #Font-Face
43
+================================================== */
44
+/* 	This is the proper syntax for an @font-face file
45
+		Just create a "fonts" folder at the root,
46
+		copy your FontName into code below and remove
47
+		comment brackets */
48
+
49
+/*	@font-face {
50
+	    font-family: 'FontName';
51
+	    src: url('../fonts/FontName.eot');
52
+	    src: url('../fonts/FontName.eot?iefix') format('eot'),
53
+	         url('../fonts/FontName.woff') format('woff'),
54
+	         url('../fonts/FontName.ttf') format('truetype'),
55
+	         url('../fonts/FontName.svg#webfontZam02nTh') format('svg');
56
+	    font-weight: normal;
57
+	    font-style: normal; }
58
+*/
0 59
\ No newline at end of file
1 60
new file mode 100644
... ...
@@ -0,0 +1,7 @@
1
+article > header > h1 {
2
+	text-align: center;
3
+}
4
+
5
+p.comment {
6
+	text-align: justify;
7
+}
0 8
new file mode 100644
... ...
@@ -0,0 +1,242 @@
1
+/*
2
+* Skeleton V1.2
3
+* Copyright 2011, Dave Gamache
4
+* www.getskeleton.com
5
+* Free to use under the MIT license.
6
+* http://www.opensource.org/licenses/mit-license.php
7
+* 6/20/2012
8
+*/
9
+
10
+
11
+/* Table of Contents
12
+==================================================
13
+    #Base 960 Grid
14
+    #Tablet (Portrait)
15
+    #Mobile (Portrait)
16
+    #Mobile (Landscape)
17
+    #Clearing */
18
+
19
+
20
+
21
+/* #Base 960 Grid
22
+================================================== */
23
+
24
+    .container                                  { position: relative; width: 960px; margin: 0 auto; padding: 0; }
25
+    .container .column,
26
+    .container .columns                         { float: left; display: inline; margin-left: 10px; margin-right: 10px; }
27
+    .row                                        { margin-bottom: 20px; }
28
+
29
+    /* Nested Column Classes */
30
+    .column.alpha, .columns.alpha               { margin-left: 0; }
31
+    .column.omega, .columns.omega               { margin-right: 0; }
32
+
33
+    /* Base Grid */
34
+    .container .one.column,
35
+    .container .one.columns                     { width: 40px;  }
36
+    .container .two.columns                     { width: 100px; }
37
+    .container .three.columns                   { width: 160px; }
38
+    .container .four.columns                    { width: 220px; }
39
+    .container .five.columns                    { width: 280px; }
40
+    .container .six.columns                     { width: 340px; }
41
+    .container .seven.columns                   { width: 400px; }
42
+    .container .eight.columns                   { width: 460px; }
43
+    .container .nine.columns                    { width: 520px; }
44
+    .container .ten.columns                     { width: 580px; }
45
+    .container .eleven.columns                  { width: 640px; }
46
+    .container .twelve.columns                  { width: 700px; }
47
+    .container .thirteen.columns                { width: 760px; }
48
+    .container .fourteen.columns                { width: 820px; }
49
+    .container .fifteen.columns                 { width: 880px; }
50
+    .container .sixteen.columns                 { width: 940px; }
51
+
52
+    .container .one-third.column                { width: 300px; }
53
+    .container .two-thirds.column               { width: 620px; }
54
+
55
+    /* Offsets */
56
+    .container .offset-by-one                   { padding-left: 60px;  }
57
+    .container .offset-by-two                   { padding-left: 120px; }
58
+    .container .offset-by-three                 { padding-left: 180px; }
59
+    .container .offset-by-four                  { padding-left: 240px; }
60
+    .container .offset-by-five                  { padding-left: 300px; }
61
+    .container .offset-by-six                   { padding-left: 360px; }
62
+    .container .offset-by-seven                 { padding-left: 420px; }
63
+    .container .offset-by-eight                 { padding-left: 480px; }
64
+    .container .offset-by-nine                  { padding-left: 540px; }
65
+    .container .offset-by-ten                   { padding-left: 600px; }
66
+    .container .offset-by-eleven                { padding-left: 660px; }
67
+    .container .offset-by-twelve                { padding-left: 720px; }
68
+    .container .offset-by-thirteen              { padding-left: 780px; }
69
+    .container .offset-by-fourteen              { padding-left: 840px; }
70
+    .container .offset-by-fifteen               { padding-left: 900px; }
71
+
72
+
73
+
74
+/* #Tablet (Portrait)
75
+================================================== */
76
+
77
+    /* Note: Design for a width of 768px */
78
+
79
+    @media only screen and (min-width: 768px) and (max-width: 959px) {
80
+        .container                                  { width: 768px; }
81
+        .container .column,
82
+        .container .columns                         { margin-left: 10px; margin-right: 10px;  }
83
+        .column.alpha, .columns.alpha               { margin-left: 0; margin-right: 10px; }
84
+        .column.omega, .columns.omega               { margin-right: 0; margin-left: 10px; }
85
+        .alpha.omega                                { margin-left: 0; margin-right: 0; }
86
+
87
+        .container .one.column,
88
+        .container .one.columns                     { width: 28px; }
89
+        .container .two.columns                     { width: 76px; }
90
+        .container .three.columns                   { width: 124px; }
91
+        .container .four.columns                    { width: 172px; }
92
+        .container .five.columns                    { width: 220px; }
93
+        .container .six.columns                     { width: 268px; }
94
+        .container .seven.columns                   { width: 316px; }
95
+        .container .eight.columns                   { width: 364px; }
96
+        .container .nine.columns                    { width: 412px; }
97
+        .container .ten.columns                     { width: 460px; }
98
+        .container .eleven.columns                  { width: 508px; }
99
+        .container .twelve.columns                  { width: 556px; }
100
+        .container .thirteen.columns                { width: 604px; }
101
+        .container .fourteen.columns                { width: 652px; }
102
+        .container .fifteen.columns                 { width: 700px; }
103
+        .container .sixteen.columns                 { width: 748px; }
104
+
105
+        .container .one-third.column                { width: 236px; }
106
+        .container .two-thirds.column               { width: 492px; }
107
+
108
+        /* Offsets */
109
+        .container .offset-by-one                   { padding-left: 48px; }
110
+        .container .offset-by-two                   { padding-left: 96px; }
111
+        .container .offset-by-three                 { padding-left: 144px; }
112
+        .container .offset-by-four                  { padding-left: 192px; }
113
+        .container .offset-by-five                  { padding-left: 240px; }
114
+        .container .offset-by-six                   { padding-left: 288px; }
115
+        .container .offset-by-seven                 { padding-left: 336px; }
116
+        .container .offset-by-eight                 { padding-left: 384px; }
117
+        .container .offset-by-nine                  { padding-left: 432px; }
118
+        .container .offset-by-ten                   { padding-left: 480px; }
119
+        .container .offset-by-eleven                { padding-left: 528px; }
120
+        .container .offset-by-twelve                { padding-left: 576px; }
121
+        .container .offset-by-thirteen              { padding-left: 624px; }
122
+        .container .offset-by-fourteen              { padding-left: 672px; }
123
+        .container .offset-by-fifteen               { padding-left: 720px; }
124
+    }
125
+
126
+
127
+/*  #Mobile (Portrait)
128
+================================================== */
129
+
130
+    /* Note: Design for a width of 320px */
131
+
132
+    @media only screen and (max-width: 767px) {
133
+        .container { width: 300px; }
134
+        .container .columns,
135
+        .container .column { margin: 0; }
136
+
137
+        .container .one.column,
138
+        .container .one.columns,
139
+        .container .two.columns,
140
+        .container .three.columns,
141
+        .container .four.columns,
142
+        .container .five.columns,
143
+        .container .six.columns,
144
+        .container .seven.columns,
145
+        .container .eight.columns,
146
+        .container .nine.columns,
147
+        .container .ten.columns,
148
+        .container .eleven.columns,
149
+        .container .twelve.columns,
150
+        .container .thirteen.columns,
151
+        .container .fourteen.columns,
152
+        .container .fifteen.columns,
153
+        .container .sixteen.columns,
154
+        .container .one-third.column,
155
+        .container .two-thirds.column  { width: 300px; }
156
+
157
+        /* Offsets */
158
+        .container .offset-by-one,
159
+        .container .offset-by-two,
160
+        .container .offset-by-three,
161
+        .container .offset-by-four,
162
+        .container .offset-by-five,
163
+        .container .offset-by-six,
164
+        .container .offset-by-seven,
165
+        .container .offset-by-eight,
166
+        .container .offset-by-nine,
167
+        .container .offset-by-ten,
168
+        .container .offset-by-eleven,
169
+        .container .offset-by-twelve,
170
+        .container .offset-by-thirteen,
171
+        .container .offset-by-fourteen,
172
+        .container .offset-by-fifteen { padding-left: 0; }
173
+
174
+    }
175
+
176
+
177
+/* #Mobile (Landscape)
178
+================================================== */
179
+
180
+    /* Note: Design for a width of 480px */
181
+
182
+    @media only screen and (min-width: 480px) and (max-width: 767px) {
183
+        .container { width: 420px; }
184
+        .container .columns,
185
+        .container .column { margin: 0; }
186
+
187
+        .container .one.column,
188
+        .container .one.columns,
189
+        .container .two.columns,
190
+        .container .three.columns,
191
+        .container .four.columns,
192
+        .container .five.columns,
193
+        .container .six.columns,
194
+        .container .seven.columns,
195
+        .container .eight.columns,
196
+        .container .nine.columns,
197
+        .container .ten.columns,
198
+        .container .eleven.columns,
199
+        .container .twelve.columns,
200
+        .container .thirteen.columns,
201
+        .container .fourteen.columns,
202
+        .container .fifteen.columns,
203
+        .container .sixteen.columns,
204
+        .container .one-third.column,
205
+        .container .two-thirds.column { width: 420px; }
206
+    }
207
+
208
+
209
+/* #Clearing
210
+================================================== */
211
+
212
+    /* Self Clearing Goodness */
213
+    .container:after { content: "\0020"; display: block; height: 0; clear: both; visibility: hidden; }
214
+
215
+    /* Use clearfix class on parent to clear nested columns,
216
+    or wrap each row of columns in a <div class="row"> */
217
+    .clearfix:before,
218
+    .clearfix:after,
219
+    .row:before,
220
+    .row:after {
221
+      content: '\0020';
222
+      display: block;
223
+      overflow: hidden;
224
+      visibility: hidden;
225
+      width: 0;
226
+      height: 0; }
227
+    .row:after,
228
+    .clearfix:after {
229
+      clear: both; }
230
+    .row,
231
+    .clearfix {
232
+      zoom: 1; }
233
+
234
+    /* You can also use a <br class="clear" /> to clear columns */
235
+    .clear {
236
+      clear: both;
237
+      display: block;
238
+      overflow: hidden;
239
+      visibility: hidden;
240
+      width: 0;
241
+      height: 0;
242
+    }
0 243
new file mode 100644
... ...
@@ -0,0 +1,11 @@
1
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/html4/frameset.dtd">
2
+<html>
3
+	<head>
4
+		<title>Article</title>
5
+	</head>
6
+	<frameset border="0" frameborder="0" framespacing="0" rows="20px, 100%">
7
+		<frame border="0" frameborder="0" name="reddit_top" noresize="1" scrolling="no" src="/toolbar/{{target_site}}"/>
8
+		<frame border="0" frameborder="0" name="inner_toolbar" src="{{target_site}}""/>
9
+	</frameset>
10
+</html>
11
+
0 12
new file mode 100644
... ...
@@ -0,0 +1,101 @@
1
+<!DOCTYPE html>
2
+<!--[if lt IE 7 ]><html class="ie ie6" lang="en"> <![endif]-->
3
+<!--[if IE 7 ]><html class="ie ie7" lang="en"> <![endif]-->
4
+<!--[if IE 8 ]><html class="ie ie8" lang="en"> <![endif]-->
5
+<!--[if (gte IE 9)|!(IE)]><!--><html lang="en"> <!--<![endif]-->
6
+<head>
7
+
8
+	<!-- Basic Page Needs
9
+  ================================================== -->
10
+	<meta charset="utf-8">
11
+   <title>{{ title }}</title>
12
+	<meta name="description" content="">
13
+	<meta name="author" content="">
14
+
15
+	<!-- Mobile Specific Metas
16
+  ================================================== -->
17
+	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
18
+
19
+	<!-- CSS
20
+  ================================================== -->
21
+	<link rel="stylesheet" href="static/stylesheets/base.css">
22
+	<link rel="stylesheet" href="static/stylesheets/skeleton.css">
23
+	<link rel="stylesheet" href="static/stylesheets/layout.css">
24
+	<link rel="stylesheet" href="static/stylesheets/mystyles.css">
25
+
26
+	<!--[if lt IE 9]>
27
+		<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
28
+	<![endif]-->
29
+
30
+	<!-- Favicons
31
+	================================================== -->
32
+	<link rel="shortcut icon" href="static/images/favicon.ico">
33
+	<link rel="apple-touch-icon" href="static/images/apple-touch-icon.png">
34
+	<link rel="apple-touch-icon" sizes="72x72" href="static/images/apple-touch-icon-72x72.png">
35
+	<link rel="apple-touch-icon" sizes="114x114" href="static/images/apple-touch-icon-114x114.png">
36
+
37
+   <style type="text/css">
38
+      h1 {
39
+         text-align: center;
40
+      }
41
+
42
+      h4 {
43
+         padding: 0.3em
44
+      }
45
+
46
+      p.entry {
47
+         vertical-align: middle;
48
+         padding: 0.25em 1em;
49
+         padding-right: 0.5em;
50
+         margin: 0em;
51
+      }
52
+
53
+      hr {
54
+         margin: 0em 0em;
55
+         margin-bottom: 0.5em;
56
+      }
57
+
58
+      section {
59
+         min-height: 20em;
60
+      }
61
+
62
+      b {
63
+         font-weight: bold;
64
+      }
65
+
66
+      .hil {
67
+         background: hsl(60, 25%, 98%);
68
+      }
69
+   </style>
70
+
71
+</head>
72
+<body>
73
+
74
+
75
+
76
+	<!-- Primary Page Layout
77
+	================================================== -->
78
+
79
+	<!-- Delete everything in this .container and get started on your own site! -->
80
+
81
+   <h1>My Links</h1>
82
+	<div class="container">
83
+      {% for column in columns %}
84
+         <section class="one-third column">
85
+         {% for title, link, sep, blog_title,img in column %}
86
+         {% if blog_title %}<h4>{%else%}<p class='entry {{ loop.cycle('','hil') }}'>{%endif%}
87
+            <a href="{{url_for('ope', url=link)}}">{{title}}</a>
88
+            {% if blog_title %}</h4>{%else%}<p>{%endif%}
89
+            {% if sep %}<hr />
90
+            {% endif %}
91
+         {% endfor %}
92
+         </section>
93
+      {% endfor %}
94
+	</div><!-- container -->
95
+
96
+
97
+<!-- End Document
98
+================================================== -->
99
+</body>
100
+</html>
101
+
0 102
new file mode 100644
... ...
@@ -0,0 +1,10 @@
1
+# Links
2
+
3
+{% for column in columns %}
4
+	{% for title, link, sep, blog_title, img in column %}
5
+{% if blog_title %}## {{title | mesc}} -- <{{link}}>
6
+{% else %}- {{title | mesc}} -- <{{link}}>{%endif%}{% if sep %}
7
+
8
+
9
+{% endif %}{% endfor %}
10
+{% endfor %}
0 11
new file mode 100644
... ...
@@ -0,0 +1,13 @@
1
+<html>
2
+    <head>            
3
+      <title>Document Title</title>
4
+		<link rel="stylesheet" href="static/stylesheets/base.css">
5
+    </head>
6
+	 <body style="margin: 0px !important">
7
+	 	<div style="margin:auto auto;width:10em;text-align:center;height:100%">
8
+		 <a href="/" target="_top" style="color:olive;vertial-align:middle">Home</a>
9
+		 <a href="{{url}}" style="color:olive;vertial-align:middle" target="_top">Linked Page</a>
10
+		</div>
11
+  	 </body>
12
+</html>
13
+
0 14
new file mode 100644
... ...
@@ -0,0 +1,75 @@
1
+import re
2
+import time
3
+import collections
4
+import urllib.request
5
+import urllib.parse
6
+import feedparser
7
+import json
8
+
9
+def urlopen(url):
10
+	headers = {'User-Agent': "the/edgent"}
11
+	req = urllib.request.Request(url, headers=headers)
12
+	return urllib.request.urlopen(req)
13
+
14
+class URLHandler(object):
15
+	registry = collections.OrderedDict()
16
+
17
+	@classmethod
18
+	def register(cls, pattern):
19
+		def _inner(ncls):
20
+			cls.registry[re.compile(pattern)] = ncls
21
+			return ncls
22
+		return _inner
23
+
24
+	@classmethod
25
+	def handle(cls, url, **args):
26
+		print(args)
27
+		for x in reversed(cls.registry):
28
+			if x.match(url):
29
+				return cls.registry[x](url).run(url, **args)
30
+
31
+	def __init__(self, url):
32
+		self.url = url
33
+
34
+	def run(self, url, **args):
35
+		data = self.get_data(url, **args)
36
+		return self.postprocess(data)
37
+
38
+	def get_data(self, url, **args): return urlopen(url)
39
+	def postprocess(self, result): return result
40
+
41
+@URLHandler.register('.')
42
+class BasicHandler(URLHandler):
43
+	def get_data(self, url, **args):
44
+		return feedparser.parse(self.url, **args)
45
+
46
+@URLHandler.register(r'^http[s]?://(www\.)?reddit.com/r/[^/]*/$')
47
+class RedditJSONHandler(URLHandler):
48
+	def get_data(self, url, **args):
49
+		result = urlopen('%s.json' % url)
50
+		result = result.read().decode(result.headers.get_content_charset())
51
+		return json.loads(result)
52
+	def postprocess(self, data):
53
+		result = feedparser.FeedParserDict()
54
+		desc = urllib.parse.urljoin(self.url, 'about.json')
55
+		desc = urlopen(desc)
56
+		desc = json.loads(desc.read().decode(desc.headers.get_content_charset()))['data']
57
+
58
+		result['feed'] = feedparser.FeedParserDict()
59
+		result.feed['title'] = desc['title']
60
+		result.feed['link'] = 'http://reddit.com/%s' % desc['url']
61
+		result['entries'] = []
62
+		result.etag = None
63
+		result.modified = None
64
+		result.status = 200
65
+
66
+		for x in data['data']['children']:
67
+			result.entries.append(feedparser.FeedParserDict())
68
+			dat = x['data']
69
+			result.entries[-1]['title'] = dat['title']
70
+			result.entries[-1]['link'] = dat['url']
71
+			result.entries[-1]['published_parsed'] = time.gmtime(dat['created_utc'])
72
+			result.entries[-1]['id'] = dat['id']
73
+
74
+		return result
75
+