Skip to content

Commit f5e8bce

Browse files
committed
Initial commit
0 parents  commit f5e8bce

23 files changed

+1302
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/build
2+
/dist
3+
/yarrharr.egg-info
4+
/yarrharr/static
5+
/static

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "assets/normalize.css"]
2+
path = assets/normalize.css
3+
url = https://github.com/necolas/normalize.css.git

COPYING

+674
Large diffs are not rendered by default.

MANIFEST.in

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include README.rst
2+
include HACKING.rst
3+
recursive-include yarrharr/static *
4+
recursive-include yarrharr/templates *.html

Makefile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# A very lazy Makefile. Just shell out to do everything. Some day this can be
2+
# come a real build system, maybe, if I feel like it…
3+
4+
export YARRHARR_CONF=yarrharr/tests/test_config.ini
5+
6+
static-assets:
7+
tools/build-css.sh
8+
9+
release: static-assets
10+
python setup.py sdist
11+
12+
test:
13+
bin/manage.py test south yarr yarrharr
14+
15+
devserver:
16+
mkdir -p static
17+
bin/manage.py runserver
18+
19+
.PHONY: static-assets release test devserver

assets/normalize.css

Submodule normalize.css added at dafaf9e

assets/yarrharr.less

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*! Yarrharr feed reader | GPLv3+ license */
2+
body {
3+
font: 16px/20px "Open Sans", sans-serif;
4+
background: white;
5+
color: #111;
6+
}
7+
8+
/* Yarr overrides */
9+
10+
/* Big, touchable buttons up top */
11+
.yarr_control {
12+
padding: 2px;
13+
border: none;
14+
}
15+
16+
/* Float the buttons so there isn't whitespace between them */
17+
.yarr_control ul {
18+
display: inline-block;
19+
}
20+
.yarr_control li {
21+
float: left;
22+
}
23+
24+
.yarr_control li a,
25+
.yarr_control li span,
26+
.yarr_control .yarr_nav a {
27+
padding: 10px 20px;
28+
background: linear-gradient(to bottom, #fefefe, white 10px, #eeeeee);
29+
color: #555;
30+
font-weight: normal;
31+
}
32+
.yarr_control li:first-child a,
33+
.yarr_control li:first-child span {
34+
border-top-left-radius: 10px;
35+
border-bottom-left-radius: 10px;
36+
}
37+
.yarr_control li:last-child a,
38+
.yarr_control li:last-child span {
39+
border-top-right-radius: 10px;
40+
border-bottom-right-radius: 10px;
41+
}
42+
.yarr_control li + li a,
43+
.yarr_control li + li span {
44+
border-left: none;
45+
}
46+
.yarr_control li a:active,
47+
.yarr_control li span:active,
48+
.yarr_control li a.yarr_selected,
49+
.yarr_control li span.yarr_selected {
50+
background: linear-gradient(to bottom, #dedede, #ddd 10px, #eeeeee);
51+
color: #101010;
52+
}
53+
54+
/* Big, touchable text buttons instead of the tiny up/down arrow icons */
55+
.yarr_previous,
56+
.yarr_next {
57+
width: auto !important;
58+
}
59+
.yarr_previous::before {
60+
content: "Prev";
61+
}
62+
.yarr_next::before {
63+
content: "Next";
64+
}
65+
66+
/* Lower-key feed list */
67+
.yarr_feed_list {
68+
border-radius: 10px;
69+
}
70+
.yarr_menu_feeds li {
71+
}
72+
.yarr_feed_list li a:link,
73+
.yarr_feed_list li a:visited {
74+
text-decoration: none;
75+
color: #555;
76+
}
77+
78+
/* Touchable read/saved checkbox controls */
79+
.yarr_entry_control label {
80+
padding: 10px;
81+
}

bin/manage.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env python
2+
3+
import os
4+
import sys
5+
6+
os.environ['DJANGO_SETTINGS_MODULE'] = 'yarrharr.settings'
7+
8+
from django.core.management import execute_from_command_line
9+
execute_from_command_line(sys.argv)

bin/yarrharr

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env python
2+
3+
import os
4+
import argparse
5+
6+
parser = argparse.ArgumentParser(description='Yarrharr feed reader')
7+
parser.add_argument('--sigstop', action='store_true', default=False,
8+
help='Send SIGSTOP to self on startup')
9+
args = parser.parse_args()
10+
11+
os.environ['DJANGO_SETTINGS_MODULE'] = 'yarrharr.settings'
12+
from yarrharr.application import run
13+
run(args.sigstop)

setup.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from setuptools import setup, find_packages
2+
3+
setup(
4+
name='yarrharr',
5+
version='0.1.0-alpha.0', # per semver.org
6+
url='https://github.com/twm/yarrharr',
7+
author='Tom Most',
8+
author_email='[email protected]',
9+
license='GPLv3+',
10+
install_requires=[
11+
'django-yarr>=0.3.6',
12+
'Twisted>=13.1.0',
13+
'South>=0.8.1',
14+
],
15+
classifiers=[
16+
'Development Status :: 3 - Alpha',
17+
'Environment :: Web Environment',
18+
'Intended Audience :: End Users/Desktop',
19+
'Framework :: Django',
20+
'Framework :: Twisted',
21+
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
22+
'Operating System :: POSIX :: Linux',
23+
'Programming Language :: Python :: 2.7',
24+
'Topic :: Internet :: WWW/HTTP',
25+
],
26+
packages=find_packages(),
27+
include_package_data=True,
28+
)

tools/build-css.sh

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
# Generate the contents of yarrharr/static from the stuff in assets.
3+
4+
set -xe
5+
6+
# Go to repo root.
7+
cd "$(dirname $0)"/..
8+
9+
# I do not feel like dealing with dependency tracking for this.
10+
rm -rf yarrharr/static
11+
mkdir -p yarrharr/static
12+
13+
# Twitter's less thinger: npm install -g recess
14+
recess \
15+
assets/normalize.css/normalize.css \
16+
assets/yarrharr.less \
17+
--compress >yarrharr/static/yarrharr.css

yarrharr/__init__.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import subprocess
2+
from pkg_resources import get_distribution, DistributionNotFound
3+
4+
__author__ = 'Tom Most <[email protected]>'
5+
6+
try:
7+
__version__ = get_distribution('yarrharr').version
8+
except DistributionNotFound:
9+
# Not installed, so this is a development git checkout. Let's have
10+
# some fun with git!
11+
last_tag = subprocess.check_output([
12+
'git', 'describe', '--match=v*'])[1:]
13+
this_commit = subprocess.check_output([
14+
'git', 'log', '-1', '--pretty=format:%h'])
15+
__version__ = '{}+dev.{}'.format(last_tag, this_commit)

yarrharr/application.py

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
Yarrharr production server via Twisted Web
3+
"""
4+
5+
import os
6+
import signal
7+
8+
from django.conf import settings
9+
from twisted.python import log
10+
from twisted.web.wsgi import WSGIResource
11+
from twisted.web.server import Site
12+
from twisted.web.static import File
13+
from twisted.web.resource import Resource
14+
from twisted.python.logfile import LogFile
15+
from twisted.internet.endpoints import serverFromString
16+
17+
from . import __version__
18+
from .wsgi import application
19+
20+
21+
class FallbackResource(Resource):
22+
"""
23+
Resource which falls back to an alternative resource tree if it doesn't
24+
have a matching child resource.
25+
"""
26+
def __init__(self, fallback):
27+
Resource.__init__(self)
28+
self.fallback = fallback
29+
30+
def render(self, request):
31+
"""
32+
Render this path with the fallback resource.
33+
"""
34+
return self.fallback.render(request)
35+
36+
def getChild(self, path, request):
37+
"""
38+
Dispatch unhandled requests to the fallback resource.
39+
"""
40+
# Mutate the request path such that it's like FallbackResource didn't handle
41+
# the request at all. This is a bit of a nasty hack, since we're
42+
# relying on the t.w.server implementation's behavior to not break when
43+
# we do this. A better way would be to create a wrapper for the request object
44+
request.postpath.insert(0, request.prepath.pop())
45+
return self.fallback
46+
47+
48+
class Root(FallbackResource):
49+
def __init__(self, reactor):
50+
wsgi = WSGIResource(reactor, reactor.getThreadPool(), application)
51+
52+
FallbackResource.__init__(self, wsgi)
53+
54+
# Install our static file handlers.
55+
self.putChild('static', File(settings.STATIC_ROOT))
56+
57+
58+
def run(sigstop=False, logPath=None):
59+
from twisted.internet import reactor
60+
if settings.LOG_SERVER is not None:
61+
# TODO: Reopen on SIGHUP so logrotate can handle compression.
62+
logfile = LogFile.fromFullPath(settings.LOG_SERVER)
63+
log.startLogging(logfile)
64+
65+
log.msg("Yarrharr {} starting".format(__version__))
66+
67+
factory = Site(Root(reactor), logPath=settings.LOG_ACCESS)
68+
endpoint = serverFromString(reactor, settings.SERVER_ENDPOINT)
69+
70+
reactor.addSystemEventTrigger('before', 'startup', endpoint.listen, factory)
71+
72+
if sigstop:
73+
def send_sigstop():
74+
"""
75+
Tell Upstart we've successfully started.
76+
"""
77+
os.kill(os.getpid(), signal.SIGSTOP)
78+
79+
reactor.addSystemEventTrigger('after', 'startup', sigstop)
80+
81+
reactor.run()

0 commit comments

Comments
 (0)