├── pastgit
├── __init__.py
├── lib
│ ├── __init__.py
│ ├── helpers.py
│ ├── app_globals.py
│ ├── pasterdao.py
│ ├── relativetime.py
│ ├── base.py
│ └── paste.py
├── model
│ ├── paste.py
│ └── __init__.py
├── config
│ ├── __init__.py
│ ├── routing.py
│ ├── environment.py
│ └── middleware.py
├── templates
│ ├── .gitignore
│ ├── dashboard.mak
│ ├── pasted.mak
│ ├── list.mak
│ ├── newpaste.mak
│ ├── editPaste.mak
│ ├── showPaste.mak
│ ├── pasteBox.mak
│ └── master.mak
├── tests
│ ├── test_models.py
│ ├── functional
│ │ ├── __init__.py
│ │ └── test_dashboard.py
│ └── __init__.py
├── controllers
│ ├── __init__.py
│ ├── template.py
│ ├── error.py
│ └── dashboard.py
├── websetup.py
└── public
│ ├── js
│ └── pastgit.js
│ └── css
│ ├── pastgit.css
│ └── common.css
├── pastgit.egg-info
├── dependency_links.txt
├── top_level.txt
├── paster_plugins.txt
├── requires.txt
├── entry_points.txt
├── PKG-INFO
└── paste_deploy_config.ini_tmpl
├── .gitignore
├── MANIFEST.in
├── docs
└── index.txt
├── README.txt
├── test.ini
├── setup.py
├── setup.cfg
└── development.ini
/pastgit/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pastgit/lib/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pastgit/model/paste.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pastgit/config/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pastgit/model/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pastgit/templates/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pastgit/tests/test_models.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pastgit/controllers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pastgit/tests/functional/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pastgit.egg-info/dependency_links.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/pastgit.egg-info/top_level.txt:
--------------------------------------------------------------------------------
1 | pastgit
2 |
--------------------------------------------------------------------------------
/pastgit.egg-info/paster_plugins.txt:
--------------------------------------------------------------------------------
1 | Pylons
2 | WebHelpers
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | pastgit.egg-info/SOURCES.txt
2 | *~
3 | *.pyc
4 | data
5 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include pastgit/public *
2 | recursive-include pastgit/templates *
3 |
--------------------------------------------------------------------------------
/pastgit.egg-info/requires.txt:
--------------------------------------------------------------------------------
1 | Pylons>=0.9.6.2
2 | GitPython>=0.1.4
3 | SQLAlchemy>=0.4.6
4 | Elixir>=0.5.2
--------------------------------------------------------------------------------
/pastgit/templates/dashboard.mak:
--------------------------------------------------------------------------------
1 | <%inherit file="master.mak"/>
2 |
3 | <%def name="title()">Dashboard%def>
4 |
--------------------------------------------------------------------------------
/pastgit.egg-info/entry_points.txt:
--------------------------------------------------------------------------------
1 |
2 | [paste.app_factory]
3 | main = pastgit.config.middleware:make_app
4 |
5 | [paste.app_install]
6 | main = pylons.util:PylonsInstaller
7 |
--------------------------------------------------------------------------------
/pastgit/lib/helpers.py:
--------------------------------------------------------------------------------
1 | """Helper functions
2 |
3 | Consists of functions to typically be used within templates, but also
4 | available to Controllers. This module is available to both as 'h'.
5 | """
6 | from webhelpers import *
7 |
--------------------------------------------------------------------------------
/pastgit/templates/pasted.mak:
--------------------------------------------------------------------------------
1 | <%inherit file="master.mak"/>
2 |
3 | <%def name="title()">Pasted%def>
4 |
5 |
Copy this link: ${c.pasteId}
--------------------------------------------------------------------------------
/pastgit.egg-info/PKG-INFO:
--------------------------------------------------------------------------------
1 | Metadata-Version: 1.0
2 | Name: pastgit
3 | Version: 0.0.0dev
4 | Summary: UNKNOWN
5 | Home-page: UNKNOWN
6 | Author: UNKNOWN
7 | Author-email: UNKNOWN
8 | License: UNKNOWN
9 | Description: UNKNOWN
10 | Platform: UNKNOWN
11 |
--------------------------------------------------------------------------------
/pastgit/tests/functional/test_dashboard.py:
--------------------------------------------------------------------------------
1 | from pastgit.tests import *
2 |
3 | class TestDashboardController(TestController):
4 |
5 | def test_index(self):
6 | response = self.app.get(url_for(controller='dashboard'))
7 | # Test response...
8 |
--------------------------------------------------------------------------------
/pastgit/templates/list.mak:
--------------------------------------------------------------------------------
1 | <%inherit file="master.mak"/>
2 |
3 | <%def name="title()">Recent pastes%def>
4 |
5 |
6 | %for i in c.ids:
7 | - ${i}
8 | %endfor
9 |
10 |
--------------------------------------------------------------------------------
/docs/index.txt:
--------------------------------------------------------------------------------
1 | pastgit
2 | +++++++
3 |
4 | This is the main index page of your documentation. It should be written in
5 | `reStructuredText format `_.
6 |
7 | You can generate your documentation in HTML format by running this command::
8 |
9 | setup.py pudge
10 |
11 | For this to work you will need to download and install ``buildutils`` and
12 | ``pudge``.
13 |
--------------------------------------------------------------------------------
/pastgit/templates/newpaste.mak:
--------------------------------------------------------------------------------
1 | <%inherit file="master.mak"/>
2 |
3 | <%def name="title()">New paste%def>
4 |
5 | ${h.start_form(h.url())}
6 |
7 | <%include file="pasteBox.mak"/>
8 |
9 |
17 | ${h.end_form}
18 |
--------------------------------------------------------------------------------
/pastgit/lib/app_globals.py:
--------------------------------------------------------------------------------
1 | """The application's Globals object"""
2 | from pylons import config
3 |
4 | class Globals(object):
5 | """Globals acts as a container for objects available throughout the
6 | life of the application
7 | """
8 |
9 | def __init__(self):
10 | """One instance of Globals is created during application
11 | initialization and is available during requests via the 'g'
12 | variable
13 | """
14 | pass
15 |
--------------------------------------------------------------------------------
/pastgit/websetup.py:
--------------------------------------------------------------------------------
1 | """Setup the pastgit application"""
2 | import logging
3 |
4 | from paste.deploy import appconfig
5 | from pylons import config
6 |
7 | from pastgit.config.environment import load_environment
8 |
9 | log = logging.getLogger(__name__)
10 |
11 | def setup_config(command, filename, section, vars):
12 | """Place any commands to setup pastgit here"""
13 | conf = appconfig('config:' + filename)
14 | load_environment(conf.global_conf, conf.local_conf)
15 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | This file is for you to describe the pastgit application. Typically
2 | you would include information such as the information below:
3 |
4 | Installation and Setup
5 | ======================
6 |
7 | Install ``pastgit`` using easy_install::
8 |
9 | easy_install pastgit
10 |
11 | Make a config file as follows::
12 |
13 | paster make-config pastgit config.ini
14 |
15 | Tweak the config file as appropriate and then setup the application::
16 |
17 | paster setup-app config.ini
18 |
19 | Then you are ready to go.
20 |
--------------------------------------------------------------------------------
/pastgit/templates/editPaste.mak:
--------------------------------------------------------------------------------
1 | <%inherit file="master.mak"/>
2 | <%!
3 | from itertools import count
4 | %>
5 |
6 | <%def name="title()">Edit Paste%def>
7 |
8 | ${h.start_form(h.url())}
9 |
10 | % for b in c.blobs:
11 | <%include file="pasteBox.mak" args="blob=b.data, blobName=b.name"/>
12 | %endfor
13 |
14 |
22 | ${h.end_form}
23 |
--------------------------------------------------------------------------------
/test.ini:
--------------------------------------------------------------------------------
1 | #
2 | # pastgit - Pylons testing environment configuration
3 | #
4 | # The %(here)s variable will be replaced with the parent directory of this file
5 | #
6 | [DEFAULT]
7 | debug = true
8 | # Uncomment and replace with the address which should receive any error reports
9 | #email_to = you@yourdomain.com
10 | smtp_server = localhost
11 | error_email_from = paste@localhost
12 |
13 | [server:main]
14 | use = egg:Paste#http
15 | host = 0.0.0.0
16 | port = 5000
17 |
18 | [app:main]
19 | use = config:development.ini
20 |
21 | # Add additional test specific configuration options as necessary.
22 |
--------------------------------------------------------------------------------
/pastgit/lib/pasterdao.py:
--------------------------------------------------------------------------------
1 | from pastgit.lib.paste import *
2 |
3 | from subprocess import Popen, PIPE
4 |
5 | from uuid import uuid4
6 |
7 | class PasterDao(object):
8 | def create(self, initial):
9 | paste = Paste("data/pastes", uuid4())
10 | paste.create(initial)
11 | return paste
12 |
13 | def get(self, id):
14 | return Paste("data/pastes", id)
15 |
16 | def list(self):
17 | ls = Popen("ls data/pastes/*.git/modified --sort=time", shell=True, stdout=PIPE)
18 | return [x[len('data/pastes/'):(len(x)-len('.git/modified'))] for x in ls.communicate()[0].split('\n') if x]
19 |
--------------------------------------------------------------------------------
/pastgit/public/js/pastgit.js:
--------------------------------------------------------------------------------
1 | // console.log("loading");
2 |
3 | function setupEvents(context) {
4 | $("a.fileName", context).click(function() {
5 | showFileName(this);
6 | });
7 |
8 | $(".addFileButton a", context).click(function() {
9 | addFile();
10 | });
11 | }
12 |
13 | $(document).ready(function() {
14 | setupEvents($(document));
15 | });
16 |
17 |
18 | function showFileName(el) {
19 | // console.log("show file name " + el);
20 |
21 | $(el).css("display", "none");
22 | $(el).prev().css("display", "block");
23 | $(el).prev().focus();
24 |
25 | $(".languageBox", $(el).parent().parent()).addClass("named");
26 | }
27 |
28 | function addFile() {
29 | // console.log("adding file");
30 | $.get("/dashboard/pasteBox", {}, function(data) {
31 | $("#files").append(data);
32 | setupEvents($("#files .file:last-child"));
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/pastgit/lib/relativetime.py:
--------------------------------------------------------------------------------
1 | import datetime as dt
2 | import time
3 |
4 | _partialMinute = 45 * 60
5 | _partialHour = 90 * 60
6 | _fullDay = 24 * 60 * 60
7 | _twoDays = _fullDay * 2
8 |
9 | def relative_time(tt):
10 | """
11 | """
12 | now = dt.datetime.utcnow()
13 |
14 | now = time.mktime(now.timetuple())
15 | date = time.mktime(tt)
16 |
17 | delta = now - date
18 |
19 | if delta < 60:
20 | return u"less than a minute ago"
21 | elif delta < 120:
22 | return u"about a minute ago"
23 | elif delta < _partialMinute:
24 | return u"%d minutes ago" % int(delta / 60)
25 | elif delta < _partialHour:
26 | return u"about an hour ago"
27 | elif delta < _fullDay:
28 | return u"%d hours ago" % int(delta / 3600)
29 | elif delta < _twoDays:
30 | return u"1 day ago"
31 | else:
32 | return u'%d days ago' % int(delta / 86400)
33 |
--------------------------------------------------------------------------------
/pastgit/controllers/template.py:
--------------------------------------------------------------------------------
1 | from pastgit.lib.base import *
2 |
3 | class TemplateController(BaseController):
4 |
5 | def view(self, url):
6 | """By default, the final controller tried to fulfill the request
7 | when no other routes match. It may be used to display a template
8 | when all else fails, e.g.::
9 |
10 | def view(self, url):
11 | return render('/%s' % url)
12 |
13 | Or if you're using Mako and want to explicitly send a 404 (Not
14 | Found) response code when the requested template doesn't exist::
15 |
16 | import mako.exceptions
17 |
18 | def view(self, url):
19 | try:
20 | return render('/%s' % url)
21 | except mako.exceptions.TopLevelLookupException:
22 | abort(404)
23 |
24 | By default this controller aborts the request with a 404 (Not
25 | Found)
26 | """
27 | abort(404)
28 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | try:
2 | from setuptools import setup, find_packages
3 | except ImportError:
4 | from ez_setup import use_setuptools
5 | use_setuptools()
6 | from setuptools import setup, find_packages
7 |
8 | setup(
9 | name='pastgit',
10 | version="",
11 | #description='',
12 | #author='',
13 | #author_email='',
14 | #url='',
15 | install_requires=["Pylons>=0.9.6.2", "GitPython>=0.1.4", "SQLAlchemy>=0.4.6", "Elixir>=0.5.2"],
16 | packages=find_packages(exclude=['ez_setup']),
17 | include_package_data=True,
18 | test_suite='nose.collector',
19 | package_data={'pastgit': ['i18n/*/LC_MESSAGES/*.mo']},
20 | #message_extractors = {'pastgit': [
21 | # ('**.py', 'python', None),
22 | # ('templates/**.mako', 'mako', None),
23 | # ('public/**', 'ignore', None)]},
24 | entry_points="""
25 | [paste.app_factory]
26 | main = pastgit.config.middleware:make_app
27 |
28 | [paste.app_install]
29 | main = pylons.util:PylonsInstaller
30 | """,
31 | )
32 |
--------------------------------------------------------------------------------
/pastgit/templates/showPaste.mak:
--------------------------------------------------------------------------------
1 | <%inherit file="master.mak"/>
2 |
3 | <%def name="title()">Show paste%def>
4 |
5 |
8 |
9 |
10 |
11 |
History
12 |
13 | %for short, id, date, revClass in c.history:
14 | - ${short} ${date}
15 | %endfor
16 |
17 |
18 |
19 |
20 |
21 | %for b in c.blobs:
22 |
23 |
24 |
${b.name}
25 |
raw
26 |
27 |
${b.data}
28 |
29 | %endfor
30 |
31 |
40 |
--------------------------------------------------------------------------------
/pastgit/lib/base.py:
--------------------------------------------------------------------------------
1 | """The base Controller API
2 |
3 | Provides the BaseController class for subclassing, and other objects
4 | utilized by Controllers.
5 | """
6 | from pylons import c, cache, config, g, request, response, session
7 | from pylons.controllers import WSGIController
8 | from pylons.controllers.util import abort, etag_cache, redirect_to
9 | from pylons.decorators import jsonify, validate
10 | from pylons.i18n import _, ungettext, N_
11 | from pylons.templating import render
12 |
13 | import pastgit.lib.helpers as h
14 | import pastgit.model as model
15 |
16 | class BaseController(WSGIController):
17 |
18 | def __call__(self, environ, start_response):
19 | """Invoke the Controller"""
20 | # WSGIController.__call__ dispatches to the Controller method
21 | # the request is routed to. This routing information is
22 | # available in environ['pylons.routes_dict']
23 | return WSGIController.__call__(self, environ, start_response)
24 |
25 | # Include the '_' function in the public names
26 | __all__ = [__name for __name in locals().keys() if not __name.startswith('_') \
27 | or __name == '_']
28 |
--------------------------------------------------------------------------------
/pastgit/templates/pasteBox.mak:
--------------------------------------------------------------------------------
1 | <%page args="blob='', blobName='', fid=0"/>
2 |
34 |
--------------------------------------------------------------------------------
/pastgit/config/routing.py:
--------------------------------------------------------------------------------
1 | """Routes configuration
2 |
3 | The more specific and detailed routes should be defined first so they
4 | may take precedent over the more generic routes. For more information
5 | refer to the routes manual at http://routes.groovie.org/docs/
6 | """
7 | from pylons import config
8 | from routes import Mapper
9 |
10 | def make_map():
11 | """Create, configure and return the routes Mapper"""
12 | map = Mapper(directory=config['pylons.paths']['controllers'],
13 | always_scan=config['debug'])
14 |
15 | # The ErrorController route (handles 404/500 error pages); it should
16 | # likely stay at the top, ensuring it can always be resolved
17 | map.connect('error/:action/:id', controller='error')
18 |
19 | # CUSTOM ROUTES HERE
20 |
21 | map.connect('', controller="dashboard")
22 | map.connect(':controller/:action/:id')
23 | map.connect(':id/:rev/raw/:file', controller="dashboard", action="raw", rev=None)
24 | map.connect(':id/raw/:file', controller="dashboard", action="raw", rev=None)
25 | map.connect(':id/:rev', controller="dashboard", action="show", rev=None)
26 |
27 | map.connect('*url', controller='template', action='view')
28 |
29 | return map
30 |
--------------------------------------------------------------------------------
/pastgit/config/environment.py:
--------------------------------------------------------------------------------
1 | """Pylons environment configuration"""
2 | import os
3 |
4 | from pylons import config
5 |
6 | import pastgit.lib.app_globals as app_globals
7 | import pastgit.lib.helpers
8 | from pastgit.config.routing import make_map
9 |
10 | def load_environment(global_conf, app_conf):
11 | """Configure the Pylons environment via the ``pylons.config``
12 | object
13 | """
14 | # Pylons paths
15 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
16 | paths = dict(root=root,
17 | controllers=os.path.join(root, 'controllers'),
18 | static_files=os.path.join(root, 'public'),
19 | templates=[os.path.join(root, 'templates')])
20 |
21 | # Initialize config with the basic options
22 | config.init_app(global_conf, app_conf, package='pastgit',
23 | template_engine='mako', paths=paths)
24 |
25 | config['routes.map'] = make_map()
26 | config['pylons.g'] = app_globals.Globals()
27 | config['pylons.h'] = pastgit.lib.helpers
28 |
29 | # Customize templating options via this variable
30 | tmpl_options = config['buffet.template_options']
31 |
32 | # CONFIGURATION OPTIONS HERE (note: all config options will override
33 | # any Pylons config options)
34 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [egg_info]
2 | tag_build = dev
3 | tag_svn_revision = true
4 |
5 | [easy_install]
6 | find_links = http://www.pylonshq.com/download/
7 |
8 | [pudge]
9 | theme = pythonpaste.org
10 |
11 | # Add extra doc files here with spaces between them
12 | docs = docs/index.txt
13 |
14 | # Doc Settings
15 | doc_base = docs/
16 | dest = docs/html
17 |
18 | # Add extra modules here separated with commas
19 | modules = pastgit
20 | title = Pastgit
21 | organization = Pylons
22 |
23 | # Highlight code-block sections with Pygments
24 | highlighter = pygments
25 |
26 | # Optionally add extra links
27 | #organization_url = http://pylonshq.com/
28 | #trac_url = http://pylonshq.com/project
29 | settings = no_about=true
30 |
31 | # Optionally add extra settings
32 | # link1=/community/ Community
33 | # link2=/download/ Download
34 |
35 | [publish]
36 | doc-dir=docs/html
37 | make-dirs=1
38 |
39 | # Babel configuration
40 | [compile_catalog]
41 | domain = pastgit
42 | directory = pastgit/i18n
43 | statistics = true
44 |
45 | [extract_messages]
46 | add_comments = TRANSLATORS:
47 | output_file = pastgit/i18n/pastgit.pot
48 | width = 80
49 |
50 | [init_catalog]
51 | domain = pastgit
52 | input_file = pastgit/i18n/pastgit.pot
53 | output_dir = pastgit/i18n
54 |
55 | [update_catalog]
56 | domain = pastgit
57 | input_file = pastgit/i18n/pastgit.pot
58 | output_dir = pastgit/i18n
59 | previous = true
60 |
--------------------------------------------------------------------------------
/pastgit/tests/__init__.py:
--------------------------------------------------------------------------------
1 | """Pylons application test package
2 |
3 | When the test runner finds and executes tests within this directory,
4 | this file will be loaded to setup the test environment.
5 |
6 | It registers the root directory of the project in sys.path and
7 | pkg_resources, in case the project hasn't been installed with
8 | setuptools. It also initializes the application via websetup (paster
9 | setup-app) with the project's test.ini configuration file.
10 | """
11 | import os
12 | import sys
13 | from unittest import TestCase
14 |
15 | import pkg_resources
16 | import paste.fixture
17 | import paste.script.appinstall
18 | from paste.deploy import loadapp
19 | from routes import url_for
20 |
21 | __all__ = ['url_for', 'TestController']
22 |
23 | here_dir = os.path.dirname(os.path.abspath(__file__))
24 | conf_dir = os.path.dirname(os.path.dirname(here_dir))
25 |
26 | sys.path.insert(0, conf_dir)
27 | pkg_resources.working_set.add_entry(conf_dir)
28 | pkg_resources.require('Paste')
29 | pkg_resources.require('PasteScript')
30 |
31 | test_file = os.path.join(conf_dir, 'test.ini')
32 | cmd = paste.script.appinstall.SetupCommand('setup-app')
33 | cmd.run([test_file])
34 |
35 | class TestController(TestCase):
36 |
37 | def __init__(self, *args, **kwargs):
38 | wsgiapp = loadapp('config:test.ini', relative_to=conf_dir)
39 | self.app = paste.fixture.TestApp(wsgiapp)
40 | TestCase.__init__(self, *args, **kwargs)
41 |
--------------------------------------------------------------------------------
/pastgit/templates/master.mak:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%def name="title()">default title%def>
5 | <%def name="subtitle()">%def>
6 |
7 |
8 |
9 | ${self.title()}
10 |
11 |
12 |
13 |
14 | ${h.stylesheet_link_tag("/css/common.css")}
15 | ${h.stylesheet_link_tag("/css/pastgit.css")}
16 |
17 |
18 |
24 |
25 |
26 | % if session.get('message'):
27 |
28 | ${session['message'].message}
29 | <%
30 | session['message'] = None
31 | session.save()
32 | %>
33 |
34 | % endif
35 |
36 |
37 | ${self.subtitle()}
38 |
39 | ${next.body()}
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/pastgit.egg-info/paste_deploy_config.ini_tmpl:
--------------------------------------------------------------------------------
1 | #
2 | # pastgit - Pylons configuration
3 | #
4 | # The %(here)s variable will be replaced with the parent directory of this file
5 | #
6 | [DEFAULT]
7 | debug = true
8 | email_to = you@yourdomain.com
9 | smtp_server = localhost
10 | error_email_from = paste@localhost
11 |
12 | [server:main]
13 | use = egg:Paste#http
14 | host = 0.0.0.0
15 | port = 5000
16 |
17 | [app:main]
18 | use = egg:pastgit
19 | full_stack = true
20 | cache_dir = %(here)s/data
21 | beaker.session.key = pastgit
22 | beaker.session.secret = ${app_instance_secret}
23 | app_instance_uuid = ${app_instance_uuid}
24 |
25 | # If you'd like to fine-tune the individual locations of the cache data dirs
26 | # for the Cache data, or the Session saves, un-comment the desired settings
27 | # here:
28 | #beaker.cache.data_dir = %(here)s/data/cache
29 | #beaker.session.data_dir = %(here)s/data/sessions
30 |
31 | # WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
32 | # Debug mode will enable the interactive debugging tool, allowing ANYONE to
33 | # execute malicious code after an exception is raised.
34 | set debug = false
35 |
36 |
37 | # Logging configuration
38 | [loggers]
39 | keys = root
40 |
41 | [handlers]
42 | keys = console
43 |
44 | [formatters]
45 | keys = generic
46 |
47 | [logger_root]
48 | level = INFO
49 | handlers = console
50 |
51 | [handler_console]
52 | class = StreamHandler
53 | args = (sys.stderr,)
54 | level = NOTSET
55 | formatter = generic
56 |
57 | [formatter_generic]
58 | format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s
59 |
--------------------------------------------------------------------------------
/pastgit/controllers/error.py:
--------------------------------------------------------------------------------
1 | import cgi
2 | import os.path
3 |
4 | from paste.urlparser import StaticURLParser
5 | from pylons.middleware import error_document_template, media_path
6 |
7 | from pastgit.lib.base import *
8 |
9 | class ErrorController(BaseController):
10 | """Generates error documents as and when they are required.
11 |
12 | The ErrorDocuments middleware forwards to ErrorController when error
13 | related status codes are returned from the application.
14 |
15 | This behaviour can be altered by changing the parameters to the
16 | ErrorDocuments middleware in your config/middleware.py file.
17 |
18 | """
19 | def document(self):
20 | """Render the error document"""
21 | page = error_document_template % \
22 | dict(prefix=request.environ.get('SCRIPT_NAME', ''),
23 | code=cgi.escape(request.params.get('code', '')),
24 | message=cgi.escape(request.params.get('message', '')))
25 | return page
26 |
27 | def img(self, id):
28 | """Serve Pylons' stock images"""
29 | return self._serve_file(os.path.join(media_path, 'img'), id)
30 |
31 | def style(self, id):
32 | """Serve Pylons' stock stylesheets"""
33 | return self._serve_file(os.path.join(media_path, 'style'), id)
34 |
35 | def _serve_file(self, root, path):
36 | """Call Paste's FileApp (a WSGI application) to serve the file
37 | at the specified path
38 | """
39 | static = StaticURLParser(root)
40 | request.environ['PATH_INFO'] = '/%s' % path
41 | return static(request.environ, self.start_response)
42 |
--------------------------------------------------------------------------------
/development.ini:
--------------------------------------------------------------------------------
1 | #
2 | # pastgit - Pylons development environment configuration
3 | #
4 | # The %(here)s variable will be replaced with the parent directory of this file
5 | #
6 | [DEFAULT]
7 | debug = true
8 | # Uncomment and replace with the address which should receive any error reports
9 | #email_to = you@yourdomain.com
10 | smtp_server = localhost
11 | error_email_from = paste@localhost
12 |
13 | [server:main]
14 | use = egg:Paste#http
15 | host = 0.0.0.0
16 | port = 5000
17 |
18 | [app:main]
19 | use = egg:pastgit
20 | full_stack = true
21 | cache_dir = %(here)s/data
22 | beaker.session.key = pastgit
23 | beaker.session.secret = somesecret
24 |
25 | # If you'd like to fine-tune the individual locations of the cache data dirs
26 | # for the Cache data, or the Session saves, un-comment the desired settings
27 | # here:
28 | #beaker.cache.data_dir = %(here)s/data/cache
29 | #beaker.session.data_dir = %(here)s/data/sessions
30 |
31 | # WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
32 | # Debug mode will enable the interactive debugging tool, allowing ANYONE to
33 | # execute malicious code after an exception is raised.
34 | #set debug = false
35 |
36 |
37 | # Logging configuration
38 | [loggers]
39 | keys = root, pastgit
40 |
41 | [handlers]
42 | keys = console
43 |
44 | [formatters]
45 | keys = generic
46 |
47 | [logger_root]
48 | level = INFO
49 | handlers = console
50 |
51 | [logger_pastgit]
52 | level = DEBUG
53 | handlers =
54 | qualname = pastgit
55 |
56 | [handler_console]
57 | class = StreamHandler
58 | args = (sys.stderr,)
59 | level = NOTSET
60 | formatter = generic
61 |
62 | [formatter_generic]
63 | format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
64 | datefmt = %H:%M:%S
65 |
--------------------------------------------------------------------------------
/pastgit/config/middleware.py:
--------------------------------------------------------------------------------
1 | """Pylons middleware initialization"""
2 | from paste.cascade import Cascade
3 | from paste.registry import RegistryManager
4 | from paste.urlparser import StaticURLParser
5 | from paste.deploy.converters import asbool
6 |
7 | from pylons import config
8 | from pylons.error import error_template
9 | from pylons.middleware import error_mapper, ErrorDocuments, ErrorHandler, \
10 | StaticJavascripts
11 | from pylons.wsgiapp import PylonsApp
12 |
13 | from pastgit.config.environment import load_environment
14 |
15 | def make_app(global_conf, full_stack=True, **app_conf):
16 | """Create a Pylons WSGI application and return it
17 |
18 | ``global_conf``
19 | The inherited configuration for this application. Normally from
20 | the [DEFAULT] section of the Paste ini file.
21 |
22 | ``full_stack``
23 | Whether or not this application provides a full WSGI stack (by
24 | default, meaning it handles its own exceptions and errors).
25 | Disable full_stack when this application is "managed" by
26 | another WSGI middleware.
27 |
28 | ``app_conf``
29 | The application's local configuration. Normally specified in the
30 | [app:] section of the Paste ini file (where
31 | defaults to main).
32 | """
33 | # Configure the Pylons environment
34 | load_environment(global_conf, app_conf)
35 |
36 | # The Pylons WSGI app
37 | app = PylonsApp()
38 |
39 | # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
40 |
41 | if asbool(full_stack):
42 | # Handle Python exceptions
43 | app = ErrorHandler(app, global_conf, error_template=error_template,
44 | **config['pylons.errorware'])
45 |
46 | # Display error documents for 401, 403, 404 status codes (and
47 | # 500 when debug is disabled)
48 | app = ErrorDocuments(app, global_conf, mapper=error_mapper, **app_conf)
49 |
50 | # Establish the Registry for this application
51 | app = RegistryManager(app)
52 |
53 | # Static files
54 | javascripts_app = StaticJavascripts()
55 | static_app = StaticURLParser(config['pylons.paths']['static_files'])
56 | app = Cascade([static_app, javascripts_app, app])
57 | return app
58 |
--------------------------------------------------------------------------------
/pastgit/public/css/pastgit.css:
--------------------------------------------------------------------------------
1 | #content input.fileName {
2 | display: none;
3 | }
4 |
5 | #content .fileName {
6 | font-family: Monaco,"Courier New",monospace;
7 | font-size: 13px;
8 | display: inline;
9 | }
10 |
11 | #content div.fileName:after {
12 | content: ':';
13 | }
14 |
15 | #content .languageBox {
16 | float: right;
17 | }
18 |
19 | #content .file {
20 | margin-right: 40px;
21 | }
22 |
23 | #content .file textarea {
24 | height: 25em;
25 | width: 100%;
26 | }
27 |
28 | #content .addFileButton {
29 | float:left;
30 | }
31 |
32 | #content .pasteButton {
33 | float:right;
34 | text-align:right;
35 | margin-right: 40px;
36 | }
37 |
38 | #content .fileContent {
39 | margin-bottom: 10px;
40 | }
41 |
42 | #content .show .fileContent {
43 | background-color: #eee;
44 | border: 1px solid #ddd;
45 | padding-left: 1em;
46 | /* white-space: pre; */
47 | font-family: 'Bitstream Vera Sans Mono','Courier',monospace;
48 | }
49 |
50 | #content #sideBox {
51 | float: right;
52 | width: 180px;
53 | }
54 |
55 | #content .show {
56 | margin-right: 180px;
57 | }
58 |
59 | #content #history ul {
60 | padding: 0px;
61 |
62 | list-style-type: none;
63 | font-family:helvetica,arial,clean,sans-serif;
64 | font-size:13.34px;
65 | }
66 |
67 | #content #history li * {
68 | vertical-align: middle;
69 | }
70 |
71 | #content a {
72 | color: blue;
73 | text-decoration: none;
74 | }
75 |
76 | #content a:hover {
77 | color: blue;
78 | text-decoration: underline;
79 | }
80 |
81 | #content #history li a {
82 | font-family: Monaco,"Courier New",monospace;
83 | font-size:70%;
84 | }
85 |
86 | #content #history li {
87 | color: #777;
88 | padding: 4px;
89 | }
90 |
91 | #content #history li.current {
92 | background-color: #eefeee;
93 | border: 1px solid #9efe9e;
94 | }
95 |
96 | #content #history li.other {
97 | padding: 5px;
98 | }
99 |
100 | #content .languageBox.named span.languageSelect {
101 | display: none;
102 | }
103 |
104 | #content .languageBox span.languageAuto {
105 | display: none;
106 | }
107 |
108 | #content .languageBox.named span.languageAuto {
109 | display: block;
110 | }
111 |
112 | #content .source pre {
113 | overflow-x: scroll;
114 | }
115 |
--------------------------------------------------------------------------------
/pastgit/public/css/common.css:
--------------------------------------------------------------------------------
1 | body, td, div, .p, a {
2 | font-family:arial,sans-serif;
3 | margin: 0px;
4 | }
5 |
6 | #nav {
7 | border-bottom: 1px solid #c9d7f1;
8 | padding-top: 2px;
9 | padding-right: 10px;
10 | padding-bottom: 4px;
11 | font-size: 84%;
12 | }
13 |
14 | #content {
15 | margin-left: 22px;
16 | }
17 |
18 | #content input,
19 | #content textarea {
20 | border: 1px solid #bebebe;
21 | width: 280px;
22 | }
23 |
24 | #content textarea[name="sshPublicKey"] {
25 | height: 6em;
26 | }
27 |
28 | #content tr.password td {
29 | padding-top: 10px;
30 | border-top: 1px solid #c9d7f1;
31 | }
32 |
33 | #messageBox .message {
34 | margin: 14px;
35 | border: 1px solid #a0bea0;
36 | background-color: #bebefe;
37 | padding: 4px;
38 | padding-left: 8px;
39 | }
40 |
41 | #messageBox .success {
42 | background-color: #befebe;
43 | }
44 |
45 | #messageBox .failure {
46 | background-color: #febebe;
47 | }
48 |
49 | .mandatory {
50 | color: red;
51 | }
52 |
53 | .doc {
54 | font-size: 90%;
55 | color: #666;
56 | }
57 |
58 | .doc a {
59 | color: #666;
60 | }
61 |
62 | span.doc {
63 | font-size: 80%;
64 | }
65 |
66 | #banner {
67 | float: right;
68 | text-align: right;
69 | padding-right: 18px;
70 |
71 | padding-top: 4px;
72 | padding-bottom: 4px;
73 |
74 | margin-top: 18px;
75 |
76 | border-left: 1px solid #c9d7f1;
77 | padding-left: 12px;
78 | margin-left: 30px;
79 | }
80 |
81 | #hosted {
82 | color: #666;
83 | font-size: 10px;
84 | padding-top: 8px;
85 | }
86 |
87 | #banner div {
88 | border-bottom: 1px solid #c9d7f1;
89 | }
90 |
91 | #banner div#capacities {
92 | padding-bottom: 2px;
93 | margin-bottom: 6px;
94 | }
95 |
96 | #banner div#einfra {
97 | padding-bottom: 8px;
98 | margin-bottom: 2px;
99 | }
100 |
101 |
102 | #banner div#hosted {
103 | border-bottom: none;
104 | padding-right: 2px;
105 | }
106 |
107 | h1#header {
108 | position: absolute;
109 | padding-top: 0px;
110 | margin-top: 0px;
111 | top: 2px;
112 | left: 2px;
113 | margin-left: 14px;
114 |
115 | font-size: 12pt;
116 | font-weight: normal;
117 | }
118 |
119 | h2 {
120 | margin-left: 14px;
121 | color: green;
122 | }
123 |
124 | h1#header span {
125 | /*border-bottom: 1px solid #c9d7f1; */
126 | }
127 |
128 |
129 | h2 span {
130 | border-bottom: 1px solid #c9d7f1;
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/pastgit/lib/paste.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import os, os.path, shutil, tempfile
4 | from git import *
5 |
6 | log = logging.getLogger(__name__)
7 |
8 | class Paste(object):
9 | def __init__(self, base, id):
10 | self.base = base
11 | self.id = id
12 | self.dirname = os.path.abspath(base + "/" + str(id) + ".git")
13 |
14 | def mkwc(self):
15 | """
16 | creates the working copy directory
17 | """
18 | self.wcname = tempfile.mkdtemp("wc")
19 |
20 | def touch(self):
21 | f = file(self.dirname + "/modified", "w")
22 | print >>f, ""
23 | f.close()
24 |
25 | def create(self, content):
26 | self.repo = Repo.create(self.dirname)
27 | self.touch()
28 |
29 | self.mkwc()
30 | try:
31 | git = Git(self.wcname)
32 | git.init()
33 |
34 | self.writeContent(content)
35 |
36 | git.add(".")
37 | git.commit(message="initial")
38 | git.push("--all", repo=self.dirname)
39 |
40 | finally:
41 | shutil.rmtree(self.wcname)
42 |
43 | def show(self, rev=None):
44 | if not rev:
45 | rev = "master"
46 | self.repo = Repo(self.dirname)
47 | return self.repo.tree(rev).values()
48 |
49 | def history(self):
50 | self.repo = Repo(self.dirname)
51 | return self.repo.commits()
52 |
53 | def modify(self, content):
54 | log.info("todo: modify" + str(content))
55 |
56 | self.mkwc()
57 | shutil.rmtree(self.wcname) # aaargh
58 | try:
59 | rep = Git(self.dirname)
60 | rep.clone(".", self.wcname)
61 |
62 | git = Git(self.wcname)
63 | wc = Repo(self.wcname)
64 |
65 | for b in wc.tree().values():
66 | os.remove(self.wcname + "/" + b.name)
67 |
68 | self.writeContent(content)
69 |
70 | git.add("--ignore-errors", ".")
71 | if git.diff("--cached"):
72 | git.commit("-a", message="web edit")
73 | git.push("--all", repo=self.dirname)
74 |
75 | self.touch()
76 | finally:
77 | shutil.rmtree(self.wcname)
78 |
79 | def writeContent(self, content):
80 | for pos, name, body, language in content:
81 | fname = name
82 | if self.isDefaultName(fname):
83 | fname = self.createDefaultName(pos, language)
84 | f = open(self.wcname + "/" + fname, "w")
85 | print >>f, body
86 | f.close()
87 |
88 | def isDefaultName(self, name):
89 | return not name or name.startswith("pastefile")
90 |
91 | def createDefaultName(self, pos, language):
92 | return "pastefile" + str(pos) + "." + language
93 |
--------------------------------------------------------------------------------
/pastgit/controllers/dashboard.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from pastgit.lib.base import *
4 | from pastgit.lib.pasterdao import *
5 | from pylons.decorators import rest
6 |
7 | from itertools import count
8 | from formencode import variabledecode
9 | from pastgit.lib.relativetime import *
10 |
11 | from pygments import highlight
12 | from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename, guess_lexer
13 | from pygments.formatters import HtmlFormatter
14 |
15 | log = logging.getLogger(__name__)
16 |
17 | class HighlightedBlob(object):
18 | pass
19 |
20 | class DashboardController(BaseController):
21 |
22 | languages = dict(txt = "Plain Text",
23 | java = "Java",
24 | js = "JavaScript",
25 | css = "CSS",
26 | xml = "XML",
27 | href = "hyperlink")
28 |
29 | def __init__(self):
30 | self.paster = PasterDao()
31 |
32 | @rest.dispatch_on(POST='_postPaste')
33 | def index(self):
34 | self._prepareLanguages()
35 | return render("newpaste")
36 |
37 | def pasteBox(self, id = None):
38 | self._prepareLanguages()
39 | return render("pasteBox")
40 |
41 | def _postPaste(self):
42 | content = self._contentFromPost(request.POST)
43 |
44 | paste = self.paster.create(content)
45 | c.pasteId = paste.id
46 |
47 | return render("pasted")
48 |
49 | def raw(self, id, rev=None, file=None):
50 | c.pasteId = id
51 |
52 | paste = self.paster.get(id)
53 | c.blobs = paste.show(rev)
54 |
55 | response.headers['content-type'] = 'text/xml; charset=utf-8'
56 |
57 | return str([x.data for x in c.blobs if x.name == file][0])
58 |
59 | def show(self, id, rev=None):
60 | c.pasteId = id
61 |
62 | paste = self.paster.get(id)
63 | c.blobs = paste.show(rev)
64 |
65 | c.blobs = [self._highlightBlob(x) for x in c.blobs]
66 |
67 | history = paste.history()
68 |
69 | c.currentRev = history[0].id
70 | if rev:
71 | c.currentRev = rev
72 |
73 | c.history = [(x.id[0:5], x.id, relative_time(x.committed_date), c.currentRev == x.id and "current" or "other") for x in history]
74 |
75 | c.editable = c.currentRev == history[0].id
76 |
77 | c.highlighterStyles = HtmlFormatter().get_style_defs('.fileContent')
78 |
79 | return render("showPaste")
80 |
81 | @rest.dispatch_on(POST='_savePaste')
82 | def edit(self, id):
83 | c.pasteId = id
84 | self._prepareLanguages()
85 |
86 | paste = self.paster.get(id)
87 | c.blobs = paste.show()
88 | return render("editPaste")
89 |
90 | def _savePaste(self, id):
91 | content = self._contentFromPost(request.POST)
92 |
93 | paste = self.paster.get(id)
94 | paste.modify(content)
95 |
96 | redirect_to(controller="/dashboard", id=id, action="show", rev=None)
97 |
98 | def _contentFromPost(self, requestPost):
99 | post = request.POST
100 |
101 | res = zip(count(), post.getall("fileName"), post.getall("fileContent"), post.getall("language"))
102 |
103 | log.info("content from post" + str(res))
104 | return res
105 |
106 | def _prepareLanguages(self):
107 | c.languages = self.languages.items()
108 |
109 | def _highlightBlob(self, blob):
110 | res = HighlightedBlob()
111 |
112 | res.id = blob.id
113 | res.name = blob.name
114 | res.data = blob.data
115 |
116 | try:
117 | lexer = guess_lexer_for_filename(blob.name, blob.data[0:1000])
118 | except:
119 | try:
120 | lexer = guess_lexer(blob.data)
121 | except:
122 | lexer = get_lexer_by_name("text")
123 |
124 | formatter = HtmlFormatter(cssclass="source")
125 | result = highlight(blob.data, lexer, formatter)
126 |
127 | res.data = result
128 |
129 | return res
130 |
131 | def list(self):
132 | c.ids = self.paster.list()
133 | return render('list')
134 |
--------------------------------------------------------------------------------