')
313 | @errors_logged
314 | def all_other_paths(path):
315 | '''
316 | '''
317 | if should_redirect():
318 | return make_redirect()
319 |
320 | if environ.get('app-logfile', None):
321 | handler = FileHandler(environ['app-logfile'])
322 | handler.setFormatter(Formatter('%(process)05s %(asctime)s %(levelname)06s: %(message)s'))
323 |
324 | else:
325 | handler = StreamHandler()
326 | handler.setFormatter(Formatter('%(process)05s %(levelname)06s: %(message)s'))
327 |
328 | getLogger('jekit').addHandler(handler)
329 |
330 | if __name__ == '__main__':
331 | app.run('0.0.0.0', debug=True)
332 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | flask
2 | requests
3 | requests-oauthlib
4 |
--------------------------------------------------------------------------------
/scripts/bookmarklet.js:
--------------------------------------------------------------------------------
1 | var href = location.href,
2 | url = false;
3 |
4 | if(href.match(/^https:\/\/github.com\/.+\/.+\/(edit|blob)\//))
5 | {
6 | if(href.match(/^.+\/_posts\/....-..-..-.+$/))
7 | {
8 | var url = href.replace(/^.+\/(.+\/.+)\/(edit|blob)\/(.+)\/_posts\/(....)-(..)-(..)-(.+)$/,
9 | 'http://host:port/$1/$3/$4/$5/$6/$7');
10 | }
11 | else
12 | {
13 | var url = href.replace(/^.+\/(.+\/.+)\/(edit|blob)\/([^\/]+\/.+)$/,
14 | 'http://host:port/$1/$3');
15 | }
16 | }
17 | else if(href.match(/^http:\/\/prose.io\/#.+\/.+\/edit\//))
18 | {
19 | if(href.match(/^.+\/_posts\/....-..-..-.+$/))
20 | {
21 | var url = href.replace(/^.+\/#(.+\/.+)\/edit\/(.+)\/_posts\/(....)-(..)-(..)-(.+)$/,
22 | 'http://host:port/$1/$2/$3/$4/$5/$6');
23 | }
24 | else
25 | {
26 | var url = href.replace(/^.+\/#(.+\/.+)\/edit\/([^\/]+\/.+)$/,
27 | 'http://host:port/$1/$2');
28 | }
29 | }
30 |
31 | if(url)
32 | {
33 | if(url && url.match(/\.(md|markdown)$/)) {
34 | url = url.replace(/\.(md|markdown)$/, '.html');
35 | }
36 |
37 | window.open(url);
38 | }
39 | else
40 | {
41 | alert("Jekit doesn't understand this URL:\n" + href);
42 | }
43 |
--------------------------------------------------------------------------------
/templates/error-404.html:
--------------------------------------------------------------------------------
1 | {% extends "page-layout.html" %}
2 | {% block content %}
3 |
4 | Jekit Error
5 |
6 |
7 | Couldn’t find {{path}} under
8 | {{account}}/{{repo}}/{{ref}} .
9 |
10 | Maybe it exists ,
11 | but Jekyll put it at some other location ?
12 |
13 |
14 | {% endblock %}
15 |
--------------------------------------------------------------------------------
/templates/error-oauth.html:
--------------------------------------------------------------------------------
1 | {% extends "page-layout.html" %}
2 | {% block content %}
3 |
4 | Jekit Error
5 |
6 |
7 | Couldn’t authenticate with Github, sorry: {{reason}}
8 |
9 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/templates/error-runtime.html:
--------------------------------------------------------------------------------
1 | {% extends "page-layout.html" %}
2 | {% block content %}
3 |
4 | Jekit Error
5 |
6 |
7 | Something went wrong building the preview. Here’s what I know:
8 |
9 |
10 | {{error}}
11 |
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "page-layout.html" %}
2 | {% block content %}
3 |
4 | Jekit
5 |
6 | Preview your Github-hosted websites before making them live. Use it to
7 | check your plain static or Jekyll -generated
8 | websites before you make them live to Github Pages
9 | or to your own server.
10 |
11 |
12 | Written by Michal Migurski
13 | with Mick Thompson ,
14 | © 2013-2014 Code for America .
15 |
16 |
17 | You can view the source code for Jekit on GitHub at https://github.com/codeforamerica/git-jekyll-preview .
18 |
19 | Check it, before you wreck it.
20 |
21 |
22 |
23 | Preview pages in Jekit by adding your Github account name and repository
24 | name to a Jekit URL:
25 |
26 |
27 | {{request.host}}/<account>/<repo>/
28 |
29 |
30 | Example showing the Beyond Transparency book site :
31 | {{request.host}}/codeforamerica/beyondtransparency/
32 |
33 |
34 |
35 |
36 | To view a commit or branch other than master , add the ref to the
37 | end:
38 |
39 |
40 | {{request.host}}/<account>/<repo>/<ref>/
41 |
42 |
43 | Example showing a single Beyond Transparency commit :
44 | {{request.host}}/codeforamerica/beyondtransparency/08463604/
45 |
46 |
47 |
48 |
49 | To view a specific file or directory, add it after the ref:
50 |
51 |
52 | {{request.host}}/<account>/<repo>/<ref>/wow/such-path.html
53 |
54 |
55 | Example of the Beyond Transparency preface :
56 | {{request.host}}/codeforamerica/beyondtransparency/master/chapters/preface/
57 |
58 |
59 |
60 | Private Repositories
61 |
62 | Jekit will ask for access to your Github account to preview private
63 | repositories. You can revoke Jekit’s access via your
64 | Github Applications page .
65 | Jekit will keep a copy of each private repository you preview for an
66 | unknown period of time.
67 |
68 | {% if id %}
69 |
76 | {% endif %}
77 | Bookmarklet
78 |
79 | From Github
80 | or Prose.io ,
81 | drag this to your bookmarks bar to install
82 | the bookmarklet .
83 |
84 |
85 | Preview on {{request.host}}
86 |
87 |
88 | {% endblock %}
89 |
--------------------------------------------------------------------------------
/templates/no-such-ref.html:
--------------------------------------------------------------------------------
1 | {% extends "page-layout.html" %}
2 | {% block content %}
3 |
4 | Jekit Error
5 |
6 |
7 | Couldn’t find {{ref}} under
8 | {{account}}/{{repo}} .
9 | Does it exist?
10 |
11 |
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/templates/no-such-repo.html:
--------------------------------------------------------------------------------
1 | {% extends "page-layout.html" %}
2 | {% block content %}
3 |
4 | Jekit Error
5 |
6 |
7 | Couldn’t find {{account}}/{{repo}} .
8 | Does it exist?
9 |
10 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/templates/page-layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Jekit
6 |
7 |
8 |
9 |
10 |
13 |
16 |
17 |
18 |
47 |
48 |
49 |
50 |
51 |
52 |
55 |
56 |
57 |
58 |
59 |
60 | {% block content %}{% endblock %}
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/util.py:
--------------------------------------------------------------------------------
1 | from os import utime, stat, listdir
2 | from fcntl import flock, LOCK_EX, LOCK_UN
3 | from contextlib import contextmanager
4 | from subprocess import Popen, PIPE
5 | from mimetypes import guess_type
6 | from traceback import format_exc
7 | from logging import getLogger
8 | from functools import wraps
9 | from os.path import join
10 | from time import time
11 |
12 | from flask import Response
13 |
14 | jlogger = getLogger('jekit')
15 |
16 | @contextmanager
17 | def locked_file(path):
18 | ''' Create a file, lock it, then unlock it. Use as a context manager.
19 |
20 | Yields nothing.
21 | '''
22 | jlogger.debug('Locking ' + path)
23 |
24 | try:
25 | file = open(path, 'a')
26 | flock(file, LOCK_EX)
27 |
28 | yield
29 |
30 | finally:
31 | jlogger.debug('Unlocking ' + path)
32 | flock(file, LOCK_UN)
33 |
34 | def is_fresh(path):
35 | ''' Return true if path is younger than 10 seconds.
36 | '''
37 | return stat(path).st_mtime > time() - 10
38 |
39 | def touch(path):
40 | ''' Touch the path to bring its modified timestamp to now.
41 | '''
42 | jlogger.debug('Touching ' + path)
43 | utime(path, None)
44 |
45 | def run_cmd(args, cwd=None):
46 | ''' Runs a single command in a new process, returns its stdout.
47 | '''
48 | command = Popen(args, stdout=PIPE, stderr=PIPE, cwd=cwd)
49 | command.wait()
50 |
51 | if command.returncode != 0:
52 | raise RuntimeError(command.stderr.read())
53 |
54 | return command.stdout.read()
55 |
56 | def get_file_response(path):
57 | ''' Return a flask Response for a simple file.
58 | '''
59 | mimetype, encoding = guess_type(path)
60 |
61 | with open(path) as file:
62 | return Response(file.read(), headers={'Content-Type': mimetype, 'Cache-Control': 'no-store private'})
63 |
64 | def get_directory_response(path):
65 | ''' Return a flask Response for a directory listing.
66 | '''
67 | names = sorted(listdir(path))
68 |
69 | if 'index.html' in names:
70 | return get_file_response(join(path, 'index.html'))
71 |
72 | items = ['%s ' % (n, n) for n in names]
73 | html = ''
74 |
75 | return Response(html, headers={'Content-Type': 'text/html', 'Cache-Control': 'no-store private'})
76 |
77 | def errors_logged(route_function):
78 | '''
79 | '''
80 | @wraps(route_function)
81 | def wrapper(*args, **kwargs):
82 | try:
83 | result = route_function(*args, **kwargs)
84 | except Exception, e:
85 | jlogger.error(format_exc())
86 | return Response('Nope.', headers={'Content-Type': 'text/plain'}, status=500)
87 | else:
88 | return result
89 |
90 | return wrapper
--------------------------------------------------------------------------------