From 361122eb22d5681c58dac731009e4814b3dd5fa5 Mon Sep 17 00:00:00 2001 From: Andi Albrecht Date: Fri, 3 Apr 2009 21:26:42 +0200 Subject: Initial import. --- extras/appengine/Makefile | 51 +++++ extras/appengine/README | 22 ++ extras/appengine/__init__.py | 0 extras/appengine/app.yaml | 27 +++ extras/appengine/examples/customers.sql | 1 + extras/appengine/examples/multiple_inserts.sql | 1 + extras/appengine/examples/pg_view.sql | 1 + extras/appengine/examples/subquery.sql | 1 + extras/appengine/examples/subquery2.sql | 1 + extras/appengine/index.yaml | 0 extras/appengine/main.py | 131 +++++++++++ extras/appengine/make_release.sh | 49 +++++ extras/appengine/settings.py | 37 ++++ extras/appengine/sqlformat/__init__.py | 0 extras/appengine/sqlformat/urls.py | 11 + extras/appengine/sqlformat/views.py | 204 +++++++++++++++++ extras/appengine/static/bg_options.png | Bin 0 -> 202 bytes extras/appengine/static/bgfieldset.png | Bin 0 -> 227 bytes extras/appengine/static/bgfooter.png | Bin 0 -> 434 bytes extras/appengine/static/bgtop.png | Bin 0 -> 430 bytes extras/appengine/static/blank.gif | Bin 0 -> 64 bytes extras/appengine/static/canvas.html | 114 ++++++++++ extras/appengine/static/hotkeys.js | 1 + extras/appengine/static/img_loading.gif | Bin 0 -> 1348 bytes .../static/jquery.textarearesizer.compressed.js | 1 + extras/appengine/static/loading.gif | Bin 0 -> 4331 bytes extras/appengine/static/lynx_screenshot.png | Bin 0 -> 66017 bytes extras/appengine/static/pygments.css | 59 +++++ extras/appengine/static/resize-grip.png | Bin 0 -> 167 bytes extras/appengine/static/robots.txt | 8 + extras/appengine/static/rpc_relay.html | 1 + extras/appengine/static/script.js | 103 +++++++++ .../appengine/static/sqlformat_client_example.py | 17 ++ extras/appengine/static/styles.css | 245 +++++++++++++++++++++ extras/appengine/templates/about.html | 44 ++++ extras/appengine/templates/api.html | 50 +++++ extras/appengine/templates/index.html | 107 +++++++++ extras/appengine/templates/master.html | 103 +++++++++ .../appengine/templates/python-client-example.html | 17 ++ extras/appengine/templates/source.html | 56 +++++ 40 files changed, 1463 insertions(+) create mode 100644 extras/appengine/Makefile create mode 100644 extras/appengine/README create mode 100644 extras/appengine/__init__.py create mode 100644 extras/appengine/app.yaml create mode 100644 extras/appengine/examples/customers.sql create mode 100644 extras/appengine/examples/multiple_inserts.sql create mode 100644 extras/appengine/examples/pg_view.sql create mode 100644 extras/appengine/examples/subquery.sql create mode 100644 extras/appengine/examples/subquery2.sql create mode 100644 extras/appengine/index.yaml create mode 100644 extras/appengine/main.py create mode 100755 extras/appengine/make_release.sh create mode 100644 extras/appengine/settings.py create mode 100644 extras/appengine/sqlformat/__init__.py create mode 100644 extras/appengine/sqlformat/urls.py create mode 100644 extras/appengine/sqlformat/views.py create mode 100644 extras/appengine/static/bg_options.png create mode 100644 extras/appengine/static/bgfieldset.png create mode 100644 extras/appengine/static/bgfooter.png create mode 100644 extras/appengine/static/bgtop.png create mode 100644 extras/appengine/static/blank.gif create mode 100644 extras/appengine/static/canvas.html create mode 100644 extras/appengine/static/hotkeys.js create mode 100644 extras/appengine/static/img_loading.gif create mode 100644 extras/appengine/static/jquery.textarearesizer.compressed.js create mode 100644 extras/appengine/static/loading.gif create mode 100644 extras/appengine/static/lynx_screenshot.png create mode 100644 extras/appengine/static/pygments.css create mode 100644 extras/appengine/static/resize-grip.png create mode 100644 extras/appengine/static/robots.txt create mode 100644 extras/appengine/static/rpc_relay.html create mode 100644 extras/appengine/static/script.js create mode 100644 extras/appengine/static/sqlformat_client_example.py create mode 100644 extras/appengine/static/styles.css create mode 100644 extras/appengine/templates/about.html create mode 100644 extras/appengine/templates/api.html create mode 100644 extras/appengine/templates/index.html create mode 100644 extras/appengine/templates/master.html create mode 100644 extras/appengine/templates/python-client-example.html create mode 100644 extras/appengine/templates/source.html (limited to 'extras/appengine') diff --git a/extras/appengine/Makefile b/extras/appengine/Makefile new file mode 100644 index 0000000..28380cf --- /dev/null +++ b/extras/appengine/Makefile @@ -0,0 +1,51 @@ +# Makefile to simplify some common AppEngine actions. +# Use 'make help' for a list of commands. + +PYTHON=`which python2.5` +DEV_APPSERVER=$(PYTHON) `which dev_appserver.py` +APPCFG=$(PYTHON) `which appcfg.py` +PORT=8080 + + +default: help + +help: + @echo "Available commands:" + @sed -n '/^[a-zA-Z0-9_.]*:/s/:.*//p' dt.refobjid)) AND (dt.classid = ('pg_rewrite'::regclass)::oid)) AND (dt.refclassid = ('pg_class'::regclass)::oid)) AND (dt.refobjid = t.oid)) AND (t.relnamespace = nt.oid)) AND (t.relkind = ANY (ARRAY['r'::"char", 'v'::"char"]))) AND pg_has_role(t.relowner, 'USAGE'::text)) ORDER BY (current_database())::information_schema.sql_identifier, (nv.nspname)::information_schema.sql_identifier, (v.relname)::information_schema.sql_identifier, (current_database())::information_schema.sql_identifier, (nt.nspname)::information_schema.sql_identifier, (t.relname)::information_schema.sql_identifier; diff --git a/extras/appengine/examples/subquery.sql b/extras/appengine/examples/subquery.sql new file mode 100644 index 0000000..dd4bbc1 --- /dev/null +++ b/extras/appengine/examples/subquery.sql @@ -0,0 +1 @@ +select sum(a1.Sales) from Store_Information a1 where a1.Store_name in (select store_name from Geography a2 where a2.store_name = a1.store_name); \ No newline at end of file diff --git a/extras/appengine/examples/subquery2.sql b/extras/appengine/examples/subquery2.sql new file mode 100644 index 0000000..6c00a87 --- /dev/null +++ b/extras/appengine/examples/subquery2.sql @@ -0,0 +1 @@ +select user_id, count(*) as how_many from bboard where not exists (select 1 from bboard_authorized_maintainers bam where bam.user_id = bboard.user_id) and posting_time + 60 > sysdate group by user_id order by how_many desc; \ No newline at end of file diff --git a/extras/appengine/index.yaml b/extras/appengine/index.yaml new file mode 100644 index 0000000..e69de29 diff --git a/extras/appengine/main.py b/extras/appengine/main.py new file mode 100644 index 0000000..d0a8418 --- /dev/null +++ b/extras/appengine/main.py @@ -0,0 +1,131 @@ +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Main program for Rietveld. + +This is also a template for running a Django app under Google App +Engine, especially when using a newer version of Django than provided +in the App Engine standard library. + +The site-specific code is all in other files: urls.py, models.py, +views.py, settings.py. +""" + +# Standard Python imports. +import os +import sys +import logging + +# Log a message each time this module get loaded. +logging.info('Loading %s, app version = %s', + __name__, os.getenv('CURRENT_VERSION_ID')) + +# Delete the preloaded copy of Django. +for key in [key for key in sys.modules if key.startswith('django')]: + del sys.modules[key] + +os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' + +# Force sys.path to have our own directory first, so we can import from it. +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) + +# Import Django from a zipfile. +sys.path.insert(0, os.path.abspath('django.zip')) + +# Fail early if we can't import Django. Log identifying information. +import django +logging.info('django.__file__ = %r, django.VERSION = %r', + django.__file__, django.VERSION) +assert django.VERSION[0] >= 1, "This Django version is too old" + +# AppEngine imports. +from google.appengine.ext.webapp import util + + +# Helper to enter the debugger. This passes in __stdin__ and +# __stdout__, because stdin and stdout are connected to the request +# and response streams. You must import this from __main__ to use it. +# (I tried to make it universally available via __builtin__, but that +# doesn't seem to work for some reason.) +def BREAKPOINT(): + import pdb + p = pdb.Pdb(None, sys.__stdin__, sys.__stdout__) + p.set_trace() + + +# Custom Django configuration. +from django.conf import settings +settings._target = None + +# Import various parts of Django. +import django.core.handlers.wsgi +import django.core.signals +import django.db +import django.dispatch.dispatcher +import django.forms + +# Work-around to avoid warning about django.newforms in djangoforms. +django.newforms = django.forms + + +def log_exception(*args, **kwds): + """Django signal handler to log an exception.""" + cls, err = sys.exc_info()[:2] + logging.exception('Exception in request: %s: %s', cls.__name__, err) + + +# Log all exceptions detected by Django. +django.core.signals.got_request_exception.connect(log_exception) + +# Unregister Django's default rollback event handler. +django.core.signals.got_request_exception.disconnect( + django.db._rollback_on_exception) + + +def real_main(): + """Main program.""" + # Create a Django application for WSGI. + application = django.core.handlers.wsgi.WSGIHandler() + # Run the WSGI CGI handler with that application. + util.run_wsgi_app(application) + + +def profile_main(): + """Main program for profiling.""" + import cProfile + import pstats + import StringIO + + prof = cProfile.Profile() + prof = prof.runctx('real_main()', globals(), locals()) + stream = StringIO.StringIO() + stats = pstats.Stats(prof, stream=stream) + # stats.strip_dirs() # Don't; too many modules are named __init__.py. + stats.sort_stats('time') # 'time', 'cumulative' or 'calls' + stats.print_stats() # Optional arg: how many to print + # The rest is optional. + # stats.print_callees() + # stats.print_callers() + print '\n
' + print '

Profile

' + print '
'
+  print stream.getvalue()[:1000000]
+  print '
' + +# Set this to profile_main to enable profiling. +main = real_main + + +if __name__ == '__main__': + main() diff --git a/extras/appengine/make_release.sh b/extras/appengine/make_release.sh new file mode 100755 index 0000000..adb8a5c --- /dev/null +++ b/extras/appengine/make_release.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +# Script to create a "release" subdirectory. This is a subdirectory +# containing a bunch of symlinks, from which the app can be updated. +# The main reason for this is to import Django from a zipfile, which +# saves dramatically in upload time: statting and computing the SHA1 +# for 1000s of files is slow. Even if most of those files don't +# actually need to be uploaded, they still add to the work done for +# each update. + +ZIPFILE=django.zip +RELEASE=release +FILES="app.yaml index.yaml __init__.py main.py settings.py" +DIRS="static templates sqlparse pygments sqlformat examples" + +# Remove old $ZIPFILE file. +rm -rf $ZIPFILE + +# Create new $ZIPFILE file. +# We prune: +# - .svn subdirectories for obvious reasons. +# - contrib/gis/ and related files because it's huge and unneeded. +# - *.po and *.mo files because they are bulky and unneeded. +# - *.pyc and *.pyo because they aren't used by App Engine anyway. +zip -q $ZIPFILE `find django/ \ + -name .svn -prune -o \ + -name gis -prune -o \ + -name admin -prune -o \ + -name localflavor -prune -o \ + -name mysql -prune -o \ + -name mysql_old -prune -o \ + -name oracle -prune -o \ + -name postgresql-prune -o \ + -name postgresql_psycopg2 -prune -o \ + -name sqlite3 -prune -o \ + -name test -prune -o \ + -type f ! -name \*.py[co] ! -name \*.[pm]o -print` + +# Remove old $RELEASE directory. +rm -rf $RELEASE + +# Create new $RELEASE directory. +mkdir $RELEASE + +# Create symbolic links. +for x in $FILES $DIRS $ZIPFILE +do + ln -s ../$x $RELEASE/$x +done diff --git a/extras/appengine/settings.py b/extras/appengine/settings.py new file mode 100644 index 0000000..fad6bb5 --- /dev/null +++ b/extras/appengine/settings.py @@ -0,0 +1,37 @@ +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Minimal Django settings.""" + +import os + +APPEND_SLASH = False +DEBUG = os.environ['SERVER_SOFTWARE'].startswith('Dev') +INSTALLED_APPS = ( + 'sqlformat', +) +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.middleware.http.ConditionalGetMiddleware', +# 'codereview.middleware.AddUserToRequestMiddleware', +) +ROOT_URLCONF = 'sqlformat.urls' +TEMPLATE_CONTEXT_PROCESSORS = () +TEMPLATE_DEBUG = DEBUG +TEMPLATE_DIRS = ( + os.path.join(os.path.dirname(__file__), 'templates'), + ) +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + ) diff --git a/extras/appengine/sqlformat/__init__.py b/extras/appengine/sqlformat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/extras/appengine/sqlformat/urls.py b/extras/appengine/sqlformat/urls.py new file mode 100644 index 0000000..c83290e --- /dev/null +++ b/extras/appengine/sqlformat/urls.py @@ -0,0 +1,11 @@ +from django.conf.urls.defaults import * + +urlpatterns = patterns( + 'sqlformat.views', + (r'^$', 'index'), + (r'^source/$', 'source'), + (r'^about/$', 'about'), + (r'^api/$', 'api'), + (r'^format/$', 'format'), + (r'^load_example', 'load_example'), +) diff --git a/extras/appengine/sqlformat/views.py b/extras/appengine/sqlformat/views.py new file mode 100644 index 0000000..d135c44 --- /dev/null +++ b/extras/appengine/sqlformat/views.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- + +import logging +import md5 +import os +import time + +from django import forms +from django.http import HttpResponse +from django.shortcuts import render_to_response +from django.utils import simplejson as json + +from google.appengine.api import users + +from pygments import highlight +from pygments.formatters import HtmlFormatter +from pygments.lexers import SqlLexer, PythonLexer, PhpLexer + +import sqlparse + + +INITIAL_SQL = "select * from foo join bar on val1 = val2 where id = 123;" +EXAMPLES_DIR = os.path.join(os.path.dirname(__file__), '../examples') + +def _get_user_image(user): + if user is None: + return None + digest = md5.new(user.email().lower()).hexdigest() + if os.environ['SERVER_SOFTWARE'].startswith('Dev'): + host = 'localhost%3A8080' + else: + host = 'sqlformat.appspot.com' + default = 'http%3A%2F%2F'+host+'%2Fstatic%2Fblank.gif' + return 'http://gravatar.com/avatar/%s?s=32&d=%s' % (digest, default) + +def _get_examples(): + fnames = os.listdir(EXAMPLES_DIR) + fnames.sort() + return fnames + + +class FormOptions(forms.Form): + data = forms.CharField(widget=forms.Textarea({'class': 'resizable'}), + initial=INITIAL_SQL, required=False) + datafile = forms.FileField(required=False) + highlight = forms.BooleanField(initial=True, required=False, + widget=forms.CheckboxInput(), + label='Enable syntax highlighting') + remove_comments = forms.BooleanField(initial=False, required=False, + widget=forms.CheckboxInput(), + label='Remove comments') + keyword_case = forms.CharField( + widget=forms.Select(choices=(('', 'Unchanged'), + ('lower', 'Lower case'), + ('upper', 'Upper case'), + ('capitalize', 'Capitalize'))), + required=False, initial='upper', label='Keywords') + identifier_case = forms.CharField( + widget=forms.Select(choices=(('', 'Unchanged'), + ('lower', 'Lower case'), + ('upper', 'Upper case'), + ('capitalize', 'Capitalize'))), + required=False, initial='', label='Identifiers') + n_indents = forms.IntegerField(min_value=0, max_value=30, + initial=2, required=False, + label='spaces', + widget=forms.TextInput({'size': 2, + 'maxlength': 2})) +# right_margin = forms.IntegerField(min_value=10, max_value=500, +# initial=60, required=False, +# label='characters', +# widget=forms.TextInput({'size': 3, +# 'maxlength': 3})) + output_format = forms.CharField( + widget=forms.Select(choices=(('sql', 'SQL'), + ('python', 'Python'), + ('php', 'PHP'), + )), + required=False, initial='sql', label='Language') + + def clean(self): + super(FormOptions, self).clean() + data = self.cleaned_data.get('data') + logging.info(self.files) + if 'datafile' in self.files: + self._datafile = self.files['datafile'].read() + else: + self._datafile = None + if not data and not self._datafile: + raise forms.ValidationError('Whoops, I need a file or text!') + elif data and self._datafile: + raise forms.ValidationError('Whoops, I need a file OR text!') + return self.cleaned_data + + def get_data(self): + data = self.cleaned_data.get('data') + if self._datafile: + return self._datafile + else: + return data + + +def format_sql(form, format='html'): + data = form.cleaned_data + popts = {} + sql = form.get_data() + if data.get('remove_comments'): + popts['strip_comments'] = True + if data.get('keyword_case'): + popts['keyword_case'] = data.get('keyword_case') + if data.get('identifier_case'): + popts['identifier_case'] = data.get('identifier_case') + if data.get('n_indents', None) is not None: + popts['reindent'] = True + popts['indent_width'] = data.get('n_indents') + if data.get('right_margin', None) is not None: + popts['right_margin'] = data.get('right_margin') + if data.get('output_format', None) is not None: + popts['output_format'] = data.get('output_format') + sql = sqlparse.format(sql, **popts) + if format in ('html', 'json'): + if data.get('highlight', False): + if popts['output_format'] == 'python': + lexer = PythonLexer() + elif popts['output_format'] == 'php': + lexer = PhpLexer() + else: + lexer = SqlLexer() + sql = highlight(sql, lexer, HtmlFormatter()) + else: + sql = ('' + % sql) + return sql + + +def index(request): + output = None + data = {} + proc_time = None + if request.method == 'POST': + logging.debug(request.POST) + form = FormOptions(request.POST, request.FILES) + if form.is_valid(): + start = time.time() + output = format_sql(form, + format=request.POST.get('format', 'html')) + proc_time = time.time()-start + else: + form = FormOptions() + if request.POST.get('format', None) == 'json': + logging.warning(form.errors) + data['errors'] = str(form.errors) + data['output'] = output + logging.info('%r', proc_time) + data['proc_time'] = '%.3f' % proc_time or 0.0 + data = json.dumps(data) + return HttpResponse(data, content_type='text/x-json') + elif request.POST.get('format', None) == 'text': + if not form.is_valid(): + data = str(form.errors) # XXX convert to plain text + else: + data = output + return HttpResponse(data, content_type='text/plain') + return render_to_response('index.html', + {'form': form, 'output': output, + 'proc_time': proc_time and '%.3f' % proc_time or None, + 'user': users.get_current_user(), + 'login_url': users.create_login_url('/'), + 'logout_url': users.create_logout_url('/'), + 'userimg': _get_user_image(users.get_current_user()), + 'examples': _get_examples()}) + + +def format(request): + if request.method == 'POST': + form = FormOptions(request.POST) + if form.is_valid(): + response = format_sql(form, format='text') + else: + response = 'ERROR: %s' % str(form.errors) + else: + response = 'POST request required' + return HttpResponse(response, content_type='text/plain') + +def source(request): + return render_to_response('source.html') + +def about(request): + return render_to_response('about.html') + +def api(request): + return render_to_response('api.html') + +def load_example(request): + fname = request.POST.get('fname') + if fname is None: + answer = 'Uups, I\'ve got no filename...' + elif fname not in _get_examples(): + answer = 'Hmm, I think you don\'t want to do that.' + else: + answer = open(os.path.join(EXAMPLES_DIR, fname)).read() + data = json.dumps({'answer': answer}) + return HttpResponse(data, content_type='text/x-json') diff --git a/extras/appengine/static/bg_options.png b/extras/appengine/static/bg_options.png new file mode 100644 index 0000000..bc1a6ed Binary files /dev/null and b/extras/appengine/static/bg_options.png differ diff --git a/extras/appengine/static/bgfieldset.png b/extras/appengine/static/bgfieldset.png new file mode 100644 index 0000000..4d55f4a Binary files /dev/null and b/extras/appengine/static/bgfieldset.png differ diff --git a/extras/appengine/static/bgfooter.png b/extras/appengine/static/bgfooter.png new file mode 100644 index 0000000..9ce5bdd Binary files /dev/null and b/extras/appengine/static/bgfooter.png differ diff --git a/extras/appengine/static/bgtop.png b/extras/appengine/static/bgtop.png new file mode 100644 index 0000000..a0d4709 Binary files /dev/null and b/extras/appengine/static/bgtop.png differ diff --git a/extras/appengine/static/blank.gif b/extras/appengine/static/blank.gif new file mode 100644 index 0000000..3be2119 Binary files /dev/null and b/extras/appengine/static/blank.gif differ diff --git a/extras/appengine/static/canvas.html b/extras/appengine/static/canvas.html new file mode 100644 index 0000000..ab642d0 --- /dev/null +++ b/extras/appengine/static/canvas.html @@ -0,0 +1,114 @@ + + + + + + + +
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/extras/appengine/static/hotkeys.js b/extras/appengine/static/hotkeys.js new file mode 100644 index 0000000..0e62a92 --- /dev/null +++ b/extras/appengine/static/hotkeys.js @@ -0,0 +1 @@ +(function(B){B.fn.__bind__=B.fn.bind;B.fn.__unbind__=B.fn.unbind;B.fn.__find__=B.fn.find;var A={version:"0.7.8",override:/keydown|keypress|keyup/g,triggersMap:{},specialKeys:{27:"esc",9:"tab",32:"space",13:"return",8:"backspace",145:"scroll",20:"capslock",144:"numlock",19:"pause",45:"insert",36:"home",46:"del",35:"end",33:"pageup",34:"pagedown",37:"left",38:"up",39:"right",40:"down",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12"},shiftNums:{"`":"~","1":"!","2":"@","3":"#","4":"$","5":"%","6":"^","7":"&","8":"*","9":"(","0":")","-":"_","=":"+",";":":","'":'"',",":"<",".":">","/":"?","\\":"|"},newTrigger:function(E,D,F){var C={};C[E]={};C[E][D]={cb:F,disableInInput:false};return C}};if(B.browser.mozilla){A.specialKeys=B.extend(A.specialKeys,{96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"})}B.fn.find=function(C){this.query=C;return B.fn.__find__.apply(this,arguments)};B.fn.unbind=function(H,E,G){if(B.isFunction(E)){G=E;E=null}if(E&&typeof E==="string"){var F=((this.prevObject&&this.prevObject.query)||(this[0].id&&this[0].id)||this[0]).toString();var D=H.split(" ");for(var C=0;C').parent().append($('
').bind("mousedown",{el:this},startDrag));var grippie=$('div.grippie',$(this).parent())[0];grippie.style.marginRight=(grippie.offsetWidth-$(this)[0].offsetWidth)+'px'})};function startDrag(e){textarea=$(e.data.el);textarea.blur();iLastMousePos=mousePosition(e).y;staticOffset=textarea.height()-iLastMousePos;textarea.css('opacity',0.25);$(document).mousemove(performDrag).mouseup(endDrag);return false}function performDrag(e){var iThisMousePos=mousePosition(e).y;var iMousePos=staticOffset+iThisMousePos;if(iLastMousePos>=(iThisMousePos)){iMousePos-=5}iLastMousePos=iThisMousePos;iMousePos=Math.max(iMin,iMousePos);textarea.height(iMousePos+'px');if(iMousePos \ No newline at end of file diff --git a/extras/appengine/static/script.js b/extras/appengine/static/script.js new file mode 100644 index 0000000..71bbabb --- /dev/null +++ b/extras/appengine/static/script.js @@ -0,0 +1,103 @@ +var initialized = false; + +function update_output() { + data = {} + data.data = $('#id_data').val(); + data.format = 'json'; + if ( $('#id_remove_comments').attr('checked') ) { + data.remove_comments = 1 + } + if ( $('#id_highlight').attr('checked') ) { data.highlight = 1 } + data.keyword_case = $('#id_keyword_case').val(); + data.identifier_case = $('#id_identifier_case').val(); + data.n_indents = $('#id_n_indents').val(); + data.right_margin = $('#id_right_margin').val(); + data.output_format = $('#id_output_format').val(); + form = document.getElementById('form_options'); + $(form.elements).attr('disabled', 'disabled'); + $('#response').addClass('loading'); + $.post('/', data, + function(data) { + if ( data.output ) { + $('#response').html(data.output); + proc_time = 'Processed in '+data.proc_time+' seconds.'; + } else { + $('#response').html('An error occured: '+data.errors); + proc_time = ''; + } + $('#proc_time').html(proc_time); + $(form.elements).each( function(idx) { + obj = $(this); + if ( ! obj.is('.keep-disabled') ) { + obj.removeAttr('disabled'); + } + }); + $('#response').removeClass('loading'); + }, 'json'); + return false; +} + +function toggle_fieldset(event) { + id = $(this).attr('id'); + $('#'+id+'_content').slideDown(); + $('legend').each(function(idx) { + obj = $('#'+this.id+'_content'); + if ( this.id != id ) { + obj.slideUp(); + } + }); +} + + +function textarea_grab_focus(evt) { + evt.stopPropagation(); + evt.preventDefault(); + $('#id_data').focus(); + return false; +} + + +function show_help() { + $('#help').toggle(); + return false; +} + + +function hide_help() { + $('#help').hide(); + return false; +} + +function load_example() { + fname = $('#sel_example').val(); + data = {fname: fname}; + $.post('/load_example', data, + function(data) { + $('#id_data').val(data.answer); + }, 'json'); +} + + +function init() { + if (initialized) { return } + //$('legend').bind('click', toggle_fieldset); + // $('legend').each(function(idx) { + // obj = $('#'+this.id+'_content'); + // if ( this.id != 'general' ) { + // obj.hide(); + // } + // }); + $(document).bind('keydown', {combi:'Ctrl+f'}, + update_output); + $('#btn_format').val('Format SQL [Ctrl+F]'); + $(document).bind('keydown', {combi: 'h', disableInInput: true}, + show_help); + $(document).bind('keydown', 'Esc', hide_help); + $(document).bind('keydown', {combi: 't', disableInInput: true}, + textarea_grab_focus); + initialized = true; + /* jQuery textarea resizer plugin usage */ + $(document).ready(function() { + $('textarea.resizable:not(.processed)').TextAreaResizer(); + }); +} \ No newline at end of file diff --git a/extras/appengine/static/sqlformat_client_example.py b/extras/appengine/static/sqlformat_client_example.py new file mode 100644 index 0000000..3b3bf0f --- /dev/null +++ b/extras/appengine/static/sqlformat_client_example.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +import urllib +import urllib2 + +payload = ( + ('data', 'select * from foo join bar on val1 = val2 where id = 123;'), + ('format', 'text'), + ('keyword_case', 'upper'), + ('reindent', True), + ('n_indents', 2), + ) + +response = urllib2.urlopen('http://sqlformat.appspot.com/format/', + urllib.urlencode(payload)) +print response.read() + diff --git a/extras/appengine/static/styles.css b/extras/appengine/static/styles.css new file mode 100644 index 0000000..41a540a --- /dev/null +++ b/extras/appengine/static/styles.css @@ -0,0 +1,245 @@ +body { + color: #000000; + background: #eeeeec; + font-family: "Free Sans", Arial, Verdana, sans; + font-size: 10pt; + margin: 0; + padding: 0; +} + +#header { + background: url(/static/bgtop.png) top left repeat-x; + border-bottom: 3px solid #2e3436; +} + +#header-inner, #main-inner, #footer-inner { + width: 70em; + margin-left: auto; + margin-right: auto; +} + + +#header-inner h1 { + margin: 0; + padding: 0; + margin-bottom: .2em; + font-weight: normal; + float: left; + font-size: 2em; + letter-spacing: .07em; +} + +#header-inner .q { + color: #f57900; + padding-right: 3px; +} + +#header-inner .q2 { + font-family: Georgia, "Times New Roman", serif; +} + +#header-inner h1 a { + text-decoration: none; + color: #eeeeec; +} + +#header-inner #slogan { + float: left; + color: #babdb6; + font-size: 1.4em; + margin-left: 1em; + letter-spacing: .18em; + padding-top: .2em; +} + + +#topmenu { + color: #729fcf; + clear: left; + padding-top: .5em; + padding-bottom: .5em; + font-size: 1.1em; +} + +#topmenu a { + color: #eeeeec; + text-decoration: none; +} + +#topmenu a:hover { + color: #ce5c00; +} + + +#main { + padding: 10px; + background: white; + line-height: 1.5em; + text-align: justify; +} + +#main form ul { + margin: 0; + padding: 0; + list-style-type: none; +} + +#main p, #main ol, #main .example, #main dl { + font-size: 12pt; + margin-left: 2em; +} + +#main dt { + font-weight: bold; +} + +#main li { + margin-bottom: .7em; +} + +#main a { + color: #f57900; +} + +#main h1, h2, h3, h4 { + color: #204a87; + font-weight: normal; + letter-spacing: .05em; +} + +#main pre, #main code.pre { + font-size: 10pt; + line-height: 1em; + padding: 4px; + background-color: #eeeeec; + border: 1px solid #babdb6; +} + +#input { + width: 50em; + float: right; + margin-left: 2em; +} + +#options { + width: 18em; + float: left; + color: #2e3436; + margin-top: .75em; + text-align: left; +} + +#options fieldset { + border: 1px solid #dddddd; + margin-bottom: .6em; + background: url(/static/bgfieldset.png) bottom left repeat-x; + -moz-border-radius: 3px; +} + + +#options input, select { + border: 1px solid #dddddd; +} + +#options .help { + font-size: .9em; + color: #888a85; + margin-bottom: .6em; +} + + +#footer { + background: url(/static/bgfooter.png) top left repeat-x; + padding: 10px; + min-height: 80px; + border-top: 4px solid #babdb6; +} + +#footer-inner { + width: 70em; + margin-left: auto; + margin-right: auto; + color: #888a85; +} + +#footer-inner a { + color: #888a85; +} + +#footer-inner a:hover { + color: #555753; +} + +.clearfix { + clear: both; +} + +.skip { + display: none; +} + +textarea { + border: 1px solid #cccccc; + border-bottom: none; + padding: 4px; + font-size: 12pt; + width: 100%; +} + +textarea:focus { + background-color: #eeeeec; +} + +div.grippie { + background: url(/static/resize-grip.png) bottom right no-repeat #eeeeec; + border-color: #cccccc; + border-style: solid; + border-width: 0pt 1px 1px; + cursor: se-resize; + height: 14px; + overflow: hidden; +} + +#help { + display: none; + position: fixed; + right: 10%; + left: 10%; + top: 0; + opacity: 0.85; + -moz-opacity: 0.85; + -khtml-opacity: 0.85; + filter: alpha(opacity=85); + -moz-border-radius: 0px 0px 10px 10px; + + background: #2e3436; + color: white; + font-weight: bold; + + padding: 1em; + z-index: 1; + overflow-x: hidden; + overflow-y: auto; +} + +#help .shortcut { + color: #f57900; + font-weight: bold; + width: 20px; + display: inline; +} + +.loading { + background: url(/static/loading.gif) top left no-repeat; +} + +.dev { + color: #cc0000; + font-size: .9em; + letter-spacing: 1; +} + +#proc_time { + color: #888a85; + font-size: .85em; +} \ No newline at end of file diff --git a/extras/appengine/templates/about.html b/extras/appengine/templates/about.html new file mode 100644 index 0000000..3222b97 --- /dev/null +++ b/extras/appengine/templates/about.html @@ -0,0 +1,44 @@ +{% extends "master.html" %} + +{% block main %} +

About this Application

+

+ This application is a online SQL formatting tool. +

+

+ Basically it's a playground for a Python module to parse and format + SQL statements. Sometimes it's easier to combine the available + options and to see the resulting output using a web front-end than + on the command line ;-) +

+

+ To get started, enter a SQL statement in the text box on the top, + choose some options and click on "Format SQL" (Ctrl+F) + to see the result. +

+

+ Note: The SQL formatter and parser is in an early stage + of development. If you're looking for a mature tool, try one of + these. +

+

Using it from the Command Line

+

+ There are three ways to use this SQL formatter from the command line: +

+
    +
  1. Grab the sources and use the module in your + Python scripts.
  2. +
  3. + Write a little script in your favorite language that sends a POST + request to this application.
    + Read the API Documentation for more information. +
  4. +
  5. Use + Lynx + +
  6. +
+ +{% endblock %} diff --git a/extras/appengine/templates/api.html b/extras/appengine/templates/api.html new file mode 100644 index 0000000..b9aaae7 --- /dev/null +++ b/extras/appengine/templates/api.html @@ -0,0 +1,50 @@ +{% extends "master.html" %} + +{% block main %} +

API Documentation

+ +

+ Using the API for this application is pretty simple. Just send a + POST request to +

+

+ http://sqlformat.appspot.com/format/ +

+ +

Options

+

+ The POST request accepts various options to control + formatting. Only the data option is required. All others + are optional. +

+ +
+
data
+
The SQL statement to format.
+
remove_comments
+
Set to 1 to remove comments.
+
keyword_case
+
How to convert keywords. Allowed values are 'lower', 'upper', + 'capitalize'.
+
identifier_case
+
How to convert identifiers. Allowed values are 'lower', 'upper', + 'capitalize'.
+
n_indents
+
An integer indicating the indendation depth.
+
right_margin
+
An integer indicating the maximum line length.
+
output_format
+
Transfer the statement into another programming language. + Allowed values are 'python', 'php'
+
+ +

Example

+

+ Here's a example in Python: +

+{% include "python-client-example.html" %} +

+ Download sqlformat_example_client.py +

+ +{% endblock %} diff --git a/extras/appengine/templates/index.html b/extras/appengine/templates/index.html new file mode 100644 index 0000000..57e7ed3 --- /dev/null +++ b/extras/appengine/templates/index.html @@ -0,0 +1,107 @@ +{% extends "master.html" %} + +{% block main %} + +{% if output %} + +{% endif %} + +
+
+ {% if form.non_field_errors %}{{form.non_field_errors}}{% endif %} +
+ Type your SQL here:
+ {{form.data}} + {% if form.data.errors %}{{form.data.errors}}{% endif %} +
+
+ ...or upload a file: + {{form.datafile}} +
+
+
+ +
+ {% if output %} +
{{output|safe}}
+ {% else %} +
+ {% endif %} +
+ {% if proc_time %}Processed in {{proc_time}} seconds.{% endif %} +
+
+ + +
+ +
+
+

Options

+
General Options +
+ {{form.remove_comments}} + +
+ {{form.highlight}} + + {% if form.highlight.errors %} +
    {{form.highlight.errors
+ {% endif %} +
+
+
+ Keywords & Identifiers +
+ {{form.keyword_case.label}}: {{form.keyword_case}} +
+
+ {{form.identifier_case.label}}: {{form.identifier_case}} +
+
+
Indentation & Margins + +
+ + {{form.n_indents}} {{form.n_indents.label}} +
Empty field means leave indentation unchanged.
+ +
+
+
Output Format + + {{form.output_format}} +
+ +
This software is in development.
+ +
+
+
+ + +{% endblock %} + diff --git a/extras/appengine/templates/master.html b/extras/appengine/templates/master.html new file mode 100644 index 0000000..4294cf4 --- /dev/null +++ b/extras/appengine/templates/master.html @@ -0,0 +1,103 @@ + + + SQLFormat - Online SQL Formatting Service + + + + + + + + + + + +
+

Keyboard Shortcuts

+

+ H - Show / hide this help window
+ Ctrl+F - Format SQL and display result
+ O - Show / hide options
+ T - Set focus on SQL input
+

+
+ + + +
+
+ {% block main %}MAIN CONTENT GOES HERE{% endblock %} +
+
+ + + + + + + + + diff --git a/extras/appengine/templates/python-client-example.html b/extras/appengine/templates/python-client-example.html new file mode 100644 index 0000000..68bf820 --- /dev/null +++ b/extras/appengine/templates/python-client-example.html @@ -0,0 +1,17 @@ +
#!/usr/bin/env python
+
+import urllib
+import urllib2
+
+payload = (
+    ('data', 'select * from foo join bar on val1 = val2 where id = 123;'),
+    ('format', 'text'),
+    ('keyword_case', 'upper'),
+    ('reindent', True),
+    ('n_indents', 2),
+    )
+
+response = urllib2.urlopen('http://sqlformat.appspot.com/format/',
+                           urllib.urlencode(payload))
+print response.read()
+
diff --git a/extras/appengine/templates/source.html b/extras/appengine/templates/source.html new file mode 100644 index 0000000..4988f2f --- /dev/null +++ b/extras/appengine/templates/source.html @@ -0,0 +1,56 @@ +{% extends "master.html" %} + +{% block main %} +
+

Source Code

+ +

Python Module

+

+ The sources for the SQL parser and formatter module are currently + hosted on Gitorious. + To clone the repository run: +

+ git clone git://github.com/andialbrecht/python-sqlparse.git +

+

+ Visit the project page + | + Browse the sources online +

+

+ Some relevant parts of the Python module contain code from the + pygments syntax highlighter. + The underlying Python module uses a non-validating SQL parser. + This approach makes it possible to parse even syntactically incorrect + SQL statements. +

+ +

+ Currently the parser module is used by + CrunchyFrog - a + database front-end for Gnome. +

+ +

+ The sqlparse module is released under the terms of the + New BSD License. +

+ +

App Engine Application

+

+ The source code for this App Engine application is available in the + examples directory of the Python module + (but it's really nothing special ;-). +

+ +

Contributing

+

+ Please file bug reports and feature requests on the project site at + http://code.google.com/p/python-sqlparse/issues/entry + or if you have code to contribute upload it to + http://codereview.appspot.com + and add albrecht.andi@googlemail.com as reviewer. +

+ +
+{% endblock %} -- cgit v1.2.1