├── .gitignore ├── MANIFEST.in ├── README.txt ├── development.ini ├── docs └── index.txt ├── pastgit.egg-info ├── PKG-INFO ├── dependency_links.txt ├── entry_points.txt ├── paste_deploy_config.ini_tmpl ├── paster_plugins.txt ├── requires.txt └── top_level.txt ├── pastgit ├── __init__.py ├── config │ ├── __init__.py │ ├── environment.py │ ├── middleware.py │ └── routing.py ├── controllers │ ├── __init__.py │ ├── dashboard.py │ ├── error.py │ └── template.py ├── lib │ ├── __init__.py │ ├── app_globals.py │ ├── base.py │ ├── helpers.py │ ├── paste.py │ ├── pasterdao.py │ └── relativetime.py ├── model │ ├── __init__.py │ └── paste.py ├── public │ ├── css │ │ ├── common.css │ │ └── pastgit.css │ └── js │ │ └── pastgit.js ├── templates │ ├── .gitignore │ ├── dashboard.mak │ ├── editPaste.mak │ ├── list.mak │ ├── master.mak │ ├── newpaste.mak │ ├── pasteBox.mak │ ├── pasted.mak │ └── showPaste.mak ├── tests │ ├── __init__.py │ ├── functional │ │ ├── __init__.py │ │ └── test_dashboard.py │ └── test_models.py └── websetup.py ├── setup.cfg ├── setup.py └── test.ini /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.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.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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.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.egg-info/paster_plugins.txt: -------------------------------------------------------------------------------- 1 | Pylons 2 | WebHelpers 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.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | pastgit 2 | -------------------------------------------------------------------------------- /pastgit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkmik/pastgit/ef573e5af8b9002ed190c226626bd63ea18d5f99/pastgit/__init__.py -------------------------------------------------------------------------------- /pastgit/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkmik/pastgit/ef573e5af8b9002ed190c226626bd63ea18d5f99/pastgit/config/__init__.py -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/controllers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkmik/pastgit/ef573e5af8b9002ed190c226626bd63ea18d5f99/pastgit/controllers/__init__.py -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pastgit/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkmik/pastgit/ef573e5af8b9002ed190c226626bd63ea18d5f99/pastgit/lib/__init__.py -------------------------------------------------------------------------------- /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/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/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/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/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/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/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkmik/pastgit/ef573e5af8b9002ed190c226626bd63ea18d5f99/pastgit/model/__init__.py -------------------------------------------------------------------------------- /pastgit/model/paste.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkmik/pastgit/ef573e5af8b9002ed190c226626bd63ea18d5f99/pastgit/model/paste.py -------------------------------------------------------------------------------- /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/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/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/templates/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkmik/pastgit/ef573e5af8b9002ed190c226626bd63ea18d5f99/pastgit/templates/.gitignore -------------------------------------------------------------------------------- /pastgit/templates/dashboard.mak: -------------------------------------------------------------------------------- 1 | <%inherit file="master.mak"/> 2 | 3 | <%def name="title()">Dashboard 4 | -------------------------------------------------------------------------------- /pastgit/templates/editPaste.mak: -------------------------------------------------------------------------------- 1 | <%inherit file="master.mak"/> 2 | <%! 3 | from itertools import count 4 | %> 5 | 6 | <%def name="title()">Edit Paste 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 |
15 |
16 | Add file 17 |
18 |
19 | 20 |
21 |
22 | ${h.end_form} 23 | -------------------------------------------------------------------------------- /pastgit/templates/list.mak: -------------------------------------------------------------------------------- 1 | <%inherit file="master.mak"/> 2 | 3 | <%def name="title()">Recent pastes 4 | 5 |
    6 | %for i in c.ids: 7 |
  • ${i}
  • 8 | %endfor 9 |
10 | -------------------------------------------------------------------------------- /pastgit/templates/master.mak: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%def name="title()">default title 5 | <%def name="subtitle()"> 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 |

Pastgit - Paste with the power of git

37 |

${self.subtitle()}

38 |
39 | ${next.body()} 40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /pastgit/templates/newpaste.mak: -------------------------------------------------------------------------------- 1 | <%inherit file="master.mak"/> 2 | 3 | <%def name="title()">New paste 4 | 5 | ${h.start_form(h.url())} 6 |
7 | <%include file="pasteBox.mak"/> 8 |
9 |
10 |
11 | Add file 12 |
13 |
14 | 15 |
16 |
17 | ${h.end_form} 18 | -------------------------------------------------------------------------------- /pastgit/templates/pasteBox.mak: -------------------------------------------------------------------------------- 1 | <%page args="blob='', blobName='', fid=0"/> 2 |
3 |
4 | <% namedClass = "named" %> 5 | %if not blobName or blobName.startswith("pastefile"): 6 | <% namedClass = "" %> 7 | %endif 8 |
9 | 10 | Language: 11 | 17 | 18 | language detected from file extension 19 |
20 |
21 | 22 | 23 | %if not (not blobName or blobName.startswith("pastefile")): 24 | ${blobName} 25 | %else: 26 | name this file 27 | %endif 28 |
29 |
30 |
31 | 32 |
33 |
34 | -------------------------------------------------------------------------------- /pastgit/templates/pasted.mak: -------------------------------------------------------------------------------- 1 | <%inherit file="master.mak"/> 2 | 3 | <%def name="title()">Pasted 4 | 5 |

Copy this link: ${c.pasteId}

-------------------------------------------------------------------------------- /pastgit/templates/showPaste.mak: -------------------------------------------------------------------------------- 1 | <%inherit file="master.mak"/> 2 | 3 | <%def name="title()">Show paste 4 | 5 | 8 | 9 | 19 | 20 |
21 | %for b in c.blobs: 22 |
23 |
24 |
${b.name}
25 | raw 26 |
27 |
${b.data}
28 |
29 | %endfor 30 |
31 |
32 | %if c.editable: 33 |
34 |
35 | 36 |
37 |
38 | %endif 39 |
40 | -------------------------------------------------------------------------------- /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/tests/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkmik/pastgit/ef573e5af8b9002ed190c226626bd63ea18d5f99/pastgit/tests/functional/__init__.py -------------------------------------------------------------------------------- /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/tests/test_models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkmik/pastgit/ef573e5af8b9002ed190c226626bd63ea18d5f99/pastgit/tests/test_models.py -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------