summaryrefslogtreecommitdiff
path: root/extras/appengine
diff options
context:
space:
mode:
authorAndi Albrecht <albrecht.andi@gmail.com>2009-04-03 21:26:42 +0200
committerAndi Albrecht <albrecht.andi@gmail.com>2009-04-03 21:26:42 +0200
commit361122eb22d5681c58dac731009e4814b3dd5fa5 (patch)
treeb096496bc9c6b8febe092d0aefd56de1a4f8f4a0 /extras/appengine
downloadsqlparse-361122eb22d5681c58dac731009e4814b3dd5fa5.tar.gz
Initial import.
Diffstat (limited to 'extras/appengine')
-rw-r--r--extras/appengine/Makefile51
-rw-r--r--extras/appengine/README22
-rw-r--r--extras/appengine/__init__.py0
-rw-r--r--extras/appengine/app.yaml27
-rw-r--r--extras/appengine/examples/customers.sql1
-rw-r--r--extras/appengine/examples/multiple_inserts.sql1
-rw-r--r--extras/appengine/examples/pg_view.sql1
-rw-r--r--extras/appengine/examples/subquery.sql1
-rw-r--r--extras/appengine/examples/subquery2.sql1
-rw-r--r--extras/appengine/index.yaml0
-rw-r--r--extras/appengine/main.py131
-rwxr-xr-xextras/appengine/make_release.sh49
-rw-r--r--extras/appengine/settings.py37
-rw-r--r--extras/appengine/sqlformat/__init__.py0
-rw-r--r--extras/appengine/sqlformat/urls.py11
-rw-r--r--extras/appengine/sqlformat/views.py204
-rw-r--r--extras/appengine/static/bg_options.pngbin0 -> 202 bytes
-rw-r--r--extras/appengine/static/bgfieldset.pngbin0 -> 227 bytes
-rw-r--r--extras/appengine/static/bgfooter.pngbin0 -> 434 bytes
-rw-r--r--extras/appengine/static/bgtop.pngbin0 -> 430 bytes
-rw-r--r--extras/appengine/static/blank.gifbin0 -> 64 bytes
-rw-r--r--extras/appengine/static/canvas.html114
-rw-r--r--extras/appengine/static/hotkeys.js1
-rw-r--r--extras/appengine/static/img_loading.gifbin0 -> 1348 bytes
-rw-r--r--extras/appengine/static/jquery.textarearesizer.compressed.js1
-rw-r--r--extras/appengine/static/loading.gifbin0 -> 4331 bytes
-rw-r--r--extras/appengine/static/lynx_screenshot.pngbin0 -> 66017 bytes
-rw-r--r--extras/appengine/static/pygments.css59
-rw-r--r--extras/appengine/static/resize-grip.pngbin0 -> 167 bytes
-rw-r--r--extras/appengine/static/robots.txt8
-rw-r--r--extras/appengine/static/rpc_relay.html1
-rw-r--r--extras/appengine/static/script.js103
-rw-r--r--extras/appengine/static/sqlformat_client_example.py17
-rw-r--r--extras/appengine/static/styles.css245
-rw-r--r--extras/appengine/templates/about.html44
-rw-r--r--extras/appengine/templates/api.html50
-rw-r--r--extras/appengine/templates/index.html107
-rw-r--r--extras/appengine/templates/master.html103
-rw-r--r--extras/appengine/templates/python-client-example.html17
-rw-r--r--extras/appengine/templates/source.html56
40 files changed, 1463 insertions, 0 deletions
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' <Makefile | sort
+
+serve:
+ $(DEV_APPSERVER) --port=$(PORT) .
+
+serve_remote:
+ $(DEV_APPSERVER) --port=$(PORT) --address 0.0.0.0 .
+
+serve_email:
+ $(DEV_APPSERVER) --port=$(PORT) --enable_sendmail .
+
+serve_remote_email:
+ $(DEV_APPSERVER) --port=$(PORT) --enable_sendmail --address 0.0.0.0 .
+
+release: make_release.sh django/.svn
+ sh make_release.sh
+
+update: release
+ $(APPCFG) update release
+
+upload: update
+
+update_indexes:
+ $(APPCFG) update_indexes .
+
+vacuum_indexes:
+ $(APPCFG) vacuum_indexes .
+
+all: django pygments sqlparse
+
+django:
+ svn co http://code.djangoproject.com/svn/django/trunk/django
+
+pygments:
+ ln -s `python -c "import pygments,os; print os.path.dirname(pygments.__file__)"` .
+
+sqlparse:
+ ln -s ../../sqlparse .
diff --git a/extras/appengine/README b/extras/appengine/README
new file mode 100644
index 0000000..4762faa
--- /dev/null
+++ b/extras/appengine/README
@@ -0,0 +1,22 @@
+gae-sqlformat - An SQL formatting tool runnging on App Engine
+=============================================================
+
+
+To set up this application run
+
+ make all
+
+This command fetches Django from Subversion and symlinks Pygments
+and sqlparse. Note: You'll need Pygments installed somewhere in your
+PYTHONPATH.
+
+For a manual setup have a look at the Makefile ;-)
+
+
+To run the development server run
+
+ make serve
+
+
+Homepage: http://sqlformat.appspot.com
+
diff --git a/extras/appengine/__init__.py b/extras/appengine/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/extras/appengine/__init__.py
diff --git a/extras/appengine/app.yaml b/extras/appengine/app.yaml
new file mode 100644
index 0000000..6aa08b2
--- /dev/null
+++ b/extras/appengine/app.yaml
@@ -0,0 +1,27 @@
+application: sqlformat
+version: 2
+runtime: python
+api_version: 1
+
+default_expiration: 7d # This is good for images, which never change
+
+handlers:
+
+- url: /(robots.txt|favicon.ico)
+ static_files: static/\1
+ upload: static/(robots.txt|favicon.ico)
+
+- url: /google7a062e78b56854c0.html
+ static_files: static/robots.txt
+ upload: static/robots.txt
+
+- url: /static/(script.js|styles.css|upload.py)
+ static_files: static/\1
+ upload: static/(script.js|styles.css|upload.py)
+ expiration: 1h # Shorter expiration, these change often
+
+- url: /static
+ static_dir: static
+
+- url: .*
+ script: main.py
diff --git a/extras/appengine/examples/customers.sql b/extras/appengine/examples/customers.sql
new file mode 100644
index 0000000..8b73850
--- /dev/null
+++ b/extras/appengine/examples/customers.sql
@@ -0,0 +1 @@
+USE mydatabase;SELECT orders.customer, orders.day_of_order, orders.product, orders.quantity as number_ordered, inventory.quantity as number_instock, inventory.price FROM orders JOIN inventory ON orders.product = inventory.product; \ No newline at end of file
diff --git a/extras/appengine/examples/multiple_inserts.sql b/extras/appengine/examples/multiple_inserts.sql
new file mode 100644
index 0000000..cf49d5d
--- /dev/null
+++ b/extras/appengine/examples/multiple_inserts.sql
@@ -0,0 +1 @@
+insert into customer (id, name) values (1, 'John');insert into customer (id, name) values (2, 'Jack');insert into customer (id, name) values (3, 'Jane');insert into customer (id, name) values (4, 'Jim');insert into customer (id, name) values (5, 'Jerry');insert into customer (id, name) values (1, 'Joe'); \ No newline at end of file
diff --git a/extras/appengine/examples/pg_view.sql b/extras/appengine/examples/pg_view.sql
new file mode 100644
index 0000000..edf9f06
--- /dev/null
+++ b/extras/appengine/examples/pg_view.sql
@@ -0,0 +1 @@
+SELECT DISTINCT (current_database())::information_schema.sql_identifier AS view_catalog, (nv.nspname)::information_schema.sql_identifier AS view_schema, (v.relname)::information_schema.sql_identifier AS view_name, (current_database())::information_schema.sql_identifier AS table_catalog, (nt.nspname)::information_schema.sql_identifier AS table_schema, (t.relname)::information_schema.sql_identifier AS table_name FROM pg_namespace nv, pg_class v, pg_depend dv, pg_depend dt, pg_class t, pg_namespace nt WHERE ((((((((((((((nv.oid = v.relnamespace) AND (v.relkind = 'v'::"char")) AND (v.oid = dv.refobjid)) AND (dv.refclassid = ('pg_class'::regclass)::oid)) AND (dv.classid = ('pg_rewrite'::regclass)::oid)) AND (dv.deptype = 'i'::"char")) AND (dv.objid = dt.objid)) AND (dv.refobjid <> 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
--- /dev/null
+++ b/extras/appengine/index.yaml
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<hr>'
+ print '<h1>Profile</h1>'
+ print '<pre>'
+ print stream.getvalue()[:1000000]
+ print '</pre>'
+
+# 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
--- /dev/null
+++ b/extras/appengine/sqlformat/__init__.py
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 = ('<textarea class="resizable" '
+ 'style="height: 350px; margin-top: 1em;">%s</textarea>'
+ % 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
--- /dev/null
+++ b/extras/appengine/static/bg_options.png
Binary files differ
diff --git a/extras/appengine/static/bgfieldset.png b/extras/appengine/static/bgfieldset.png
new file mode 100644
index 0000000..4d55f4a
--- /dev/null
+++ b/extras/appengine/static/bgfieldset.png
Binary files differ
diff --git a/extras/appengine/static/bgfooter.png b/extras/appengine/static/bgfooter.png
new file mode 100644
index 0000000..9ce5bdd
--- /dev/null
+++ b/extras/appengine/static/bgfooter.png
Binary files differ
diff --git a/extras/appengine/static/bgtop.png b/extras/appengine/static/bgtop.png
new file mode 100644
index 0000000..a0d4709
--- /dev/null
+++ b/extras/appengine/static/bgtop.png
Binary files differ
diff --git a/extras/appengine/static/blank.gif b/extras/appengine/static/blank.gif
new file mode 100644
index 0000000..3be2119
--- /dev/null
+++ b/extras/appengine/static/blank.gif
Binary files 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 @@
+<html>
+
+<head>
+<style type="text/css">
+ /*
+ These styles are customizable.
+ Provide .canvas-gadget (the div that holds the canvas mode gadget)
+ at least 500px width so that the gadget has sufficient screen real estate
+*/
+body {
+ margin: 0;
+ font-family:arial, sans-serif;
+ text-align:center;
+}
+.container {
+ width:652px;
+ margin:0 auto;
+ text-align:left;
+}
+.fc-sign-in-header {
+ text-align:left;
+ font-size: 13px;
+ padding:3px 10px;
+ border-bottom:1px solid #000000;
+}
+.signin {
+ text-align:left;
+ float:right;
+ font-size: 13px;
+ height: 32px;
+}
+.go-back {
+ text-align:left;
+ margin:5px auto 15px auto;
+}
+.go-back a, .go-back a:visited {
+ font-weight:bold;
+}
+.canvas-gadget {
+ text-align:left;
+ width:650px; /* ALLOW AT LEAST 500px WIDTH*/
+ margin:10px auto 10px auto;
+ border:1px solid #cccccc;
+}
+.site-header {
+ margin-top: 10px;
+}
+.section-title {
+ font-size: 2em;
+}
+.clear {
+ clear:both;
+ font-size:1px;
+ height:1px;
+ line-height:0;
+ margin:0;
+ padding:0;
+}
+</style>
+<script type="text/javascript" src="http://www.google.com/friendconnect/script/friendconnect.js"></script>
+</head>
+<body>
+<div class="container">
+ <div class="fc-sign-in-header">
+ <!--REQUIRED SO VISITORS CAN SIGN IN-->
+ <div class="signin" id="gadget-signin"></div>
+ <script type="text/javascript">
+ var skin = {};
+ skin['BORDER_COLOR'] = '#cccccc';
+ skin['ENDCAP_BG_COLOR'] = '#e0ecff';
+ skin['ENDCAP_TEXT_COLOR'] = '#333333';
+ skin['ENDCAP_LINK_COLOR'] = '#0000cc';
+ skin['ALTERNATE_BG_COLOR'] = '#ffffff';
+ skin['CONTENT_BG_COLOR'] = '#ffffff';
+ skin['CONTENT_LINK_COLOR'] = '#0000cc';
+ skin['CONTENT_TEXT_COLOR'] = '#333333';
+ skin['CONTENT_SECONDARY_LINK_COLOR'] = '#7777cc';
+ skin['CONTENT_SECONDARY_TEXT_COLOR'] = '#666666';
+ skin['CONTENT_HEADLINE_COLOR'] = '#333333';
+ skin['ALIGNMENT'] = 'right';
+ google.friendconnect.container.renderCanvasSignInGadget({'id': 'gadget-signin'}, skin);
+ </script>
+ <!--END REQUIRED-->
+ <div class="clear"></div>
+ </div>
+
+ <div class="site-header"><span class="section-title">Site Name</span></div>
+ <div class="go-back">
+ <!--REQUIRED SO VISITORS CAN RETURN TO REFERRING PAGE-->
+ <a href="javascript:google.friendconnect.container.goBackToSite();">
+ &lsaquo;&lsaquo; Return home</a>
+ <!--END REQUIRED-->
+ </div>
+ <!-- REQUIRED - THIS IS WHERE THE GADGET IS PRESENTED. ALLOW AT LEAST 500px WIDTH -->
+ <div id="gadget-canvas" class="canvas-gadget"></div>
+ <script type="text/javascript">
+ var skin = {};
+ skin['BORDER_COLOR'] = '#cccccc';
+ skin['ENDCAP_BG_COLOR'] = '#e0ecff';
+ skin['ENDCAP_TEXT_COLOR'] = '#333333';
+ skin['ENDCAP_LINK_COLOR'] = '#0000cc';
+ skin['ALTERNATE_BG_COLOR'] = '#ffffff';
+ skin['CONTENT_BG_COLOR'] = '#ffffff';
+ skin['CONTENT_LINK_COLOR'] = '#0000cc';
+ skin['CONTENT_TEXT_COLOR'] = '#333333';
+ skin['CONTENT_SECONDARY_LINK_COLOR'] = '#7777cc';
+ skin['CONTENT_SECONDARY_TEXT_COLOR'] = '#666666';
+ skin['CONTENT_HEADLINE_COLOR'] = '#333333';
+ google.friendconnect.container.renderUrlCanvasGadget({'id': 'gadget-canvas'}, skin);
+ </script>
+ <!--END REQUIRED-->
+</div>
+</body>
+</html> \ 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<D.length;C++){delete A.triggersMap[F][D[C]][E]}}return this.__unbind__(H,G)};B.fn.bind=function(J,F,K){var H=J.match(A.override);if(B.isFunction(F)||!H){return this.__bind__(J,F,K)}else{var N=null,I=B.trim(J.replace(A.override,""));if(I){N=this.__bind__(I,F,K)}if(typeof F==="string"){F={combi:F}}if(F.combi){for(var M=0;M<H.length;M++){var D=H[M];var G=F.combi.toLowerCase(),E=A.newTrigger(D,G,K),L=((this.prevObject&&this.prevObject.query)||(this[0].id&&this[0].id)||this[0]).toString();E[D][G].disableInInput=F.disableInInput;if(!A.triggersMap[L]){A.triggersMap[L]=E}else{if(!A.triggersMap[L][D]){A.triggersMap[L][D]=E[D]}}var C=A.triggersMap[L][D][G];if(!C){A.triggersMap[L][D][G]=[E[D][G]]}else{if(C.constructor!==Array){A.triggersMap[L][D][G]=[C]}else{A.triggersMap[L][D][G][C.length]=E[D][G]}}this.each(function(){var O=B(this);if(O.attr("hkId")&&O.attr("hkId")!==L){L=O.attr("hkId")+";"+L}O.attr("hkId",L)});N=this.__bind__(H.join(" "),F,A.handler)}}return N}};A.findElement=function(C){if(!B(C).attr("hkId")){if(B.browser.opera||B.browser.safari){while(!B(C).attr("hkId")&&C.parentNode){C=C.parentNode}}}return C};A.handler=function(E){var O=A.findElement(E.currentTarget),I=B(O),D=I.attr("hkId");if(D){D=D.split(";");var G=E.which,Q=E.type,P=A.specialKeys[G],N=!P&&String.fromCharCode(G).toLowerCase(),H=E.shiftKey,C=E.ctrlKey,M=E.altKey||E.originalEvent.altKey,F=null;for(var R=0;R<D.length;R++){if(A.triggersMap[D[R]][Q]){F=A.triggersMap[D[R]][Q];break}}if(F){var J;if(!H&&!C&&!M){J=F[P]||(N&&F[N])}else{var L="";if(M){L+="alt+"}if(C){L+="ctrl+"}if(H){L+="shift+"}J=F[L+P];if(!J){if(N){J=F[L+N]||F[L+A.shiftNums[N]]||(L==="shift+"&&F[A.shiftNums[N]])}}}if(J){var S=false;for(var R=0;R<J.length;R++){if(J[R].disableInInput){var K=B(E.target);if(I.is("input")||I.is("textarea")||K.is("input")||K.is("textarea")){return true}}S=S||J[R].cb.apply(this,[E])}return S}}}};window.hotkeys=A;return B})(jQuery); \ No newline at end of file
diff --git a/extras/appengine/static/img_loading.gif b/extras/appengine/static/img_loading.gif
new file mode 100644
index 0000000..6465823
--- /dev/null
+++ b/extras/appengine/static/img_loading.gif
Binary files differ
diff --git a/extras/appengine/static/jquery.textarearesizer.compressed.js b/extras/appengine/static/jquery.textarearesizer.compressed.js
new file mode 100644
index 0000000..5464ae6
--- /dev/null
+++ b/extras/appengine/static/jquery.textarearesizer.compressed.js
@@ -0,0 +1 @@
+(function($){var textarea,staticOffset;var iLastMousePos=0;var iMin=32;var grip;$.fn.TextAreaResizer=function(){return this.each(function(){textarea=$(this).addClass('processed'),staticOffset=null;$(this).wrap('<div class="resizable-textarea"><span></span></div>').parent().append($('<div class="grippie"></div>').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<iMin){endDrag(e)}return false}function endDrag(e){$(document).unbind('mousemove',performDrag).unbind('mouseup',endDrag);textarea.css('opacity',1);textarea.focus();textarea=null;staticOffset=null;iLastMousePos=0}function mousePosition(e){return{x:e.clientX+document.documentElement.scrollLeft,y:e.clientY+document.documentElement.scrollTop}}})(jQuery); \ No newline at end of file
diff --git a/extras/appengine/static/loading.gif b/extras/appengine/static/loading.gif
new file mode 100644
index 0000000..a879bed
--- /dev/null
+++ b/extras/appengine/static/loading.gif
Binary files differ
diff --git a/extras/appengine/static/lynx_screenshot.png b/extras/appengine/static/lynx_screenshot.png
new file mode 100644
index 0000000..d1592ac
--- /dev/null
+++ b/extras/appengine/static/lynx_screenshot.png
Binary files differ
diff --git a/extras/appengine/static/pygments.css b/extras/appengine/static/pygments.css
new file mode 100644
index 0000000..da02807
--- /dev/null
+++ b/extras/appengine/static/pygments.css
@@ -0,0 +1,59 @@
+.c { color: #408080; font-style: italic } /* Comment */
+.err { border: 1px solid #FF0000 } /* Error */
+.k { color: #008000; font-weight: bold } /* Keyword */
+.o { color: #666666 } /* Operator */
+.cm { color: #408080; font-style: italic } /* Comment.Multiline */
+.cp { color: #BC7A00 } /* Comment.Preproc */
+.c1 { color: #408080; font-style: italic } /* Comment.Single */
+.cs { color: #408080; font-style: italic } /* Comment.Special */
+.gd { color: #A00000 } /* Generic.Deleted */
+.ge { font-style: italic } /* Generic.Emph */
+.gr { color: #FF0000 } /* Generic.Error */
+.gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.gi { color: #00A000 } /* Generic.Inserted */
+.go { color: #808080 } /* Generic.Output */
+.gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.gs { font-weight: bold } /* Generic.Strong */
+.gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.gt { color: #0040D0 } /* Generic.Traceback */
+.kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.kp { color: #008000 } /* Keyword.Pseudo */
+.kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.kt { color: #B00040 } /* Keyword.Type */
+.m { color: #666666 } /* Literal.Number */
+.s { color: #BA2121 } /* Literal.String */
+.na { color: #7D9029 } /* Name.Attribute */
+.nb { color: #008000 } /* Name.Builtin */
+.nc { color: #0000FF; font-weight: bold } /* Name.Class */
+.no { color: #880000 } /* Name.Constant */
+.nd { color: #AA22FF } /* Name.Decorator */
+.ni { color: #999999; font-weight: bold } /* Name.Entity */
+.ne { color: #D2413A; font-weight: bold } /* Name.Exception */
+.nf { color: #0000FF } /* Name.Function */
+.nl { color: #A0A000 } /* Name.Label */
+.nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
+.nt { color: #008000; font-weight: bold } /* Name.Tag */
+.nv { color: #19177C } /* Name.Variable */
+.ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
+.w { color: #bbbbbb } /* Text.Whitespace */
+.mf { color: #666666 } /* Literal.Number.Float */
+.mh { color: #666666 } /* Literal.Number.Hex */
+.mi { color: #666666 } /* Literal.Number.Integer */
+.mo { color: #666666 } /* Literal.Number.Oct */
+.sb { color: #BA2121 } /* Literal.String.Backtick */
+.sc { color: #BA2121 } /* Literal.String.Char */
+.sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
+.s2 { color: #BA2121 } /* Literal.String.Double */
+.se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
+.sh { color: #BA2121 } /* Literal.String.Heredoc */
+.si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
+.sx { color: #008000 } /* Literal.String.Other */
+.sr { color: #BB6688 } /* Literal.String.Regex */
+.s1 { color: #BA2121 } /* Literal.String.Single */
+.ss { color: #19177C } /* Literal.String.Symbol */
+.bp { color: #008000 } /* Name.Builtin.Pseudo */
+.vc { color: #19177C } /* Name.Variable.Class */
+.vg { color: #19177C } /* Name.Variable.Global */
+.vi { color: #19177C } /* Name.Variable.Instance */
+.il { color: #666666 } /* Literal.Number.Integer.Long */
diff --git a/extras/appengine/static/resize-grip.png b/extras/appengine/static/resize-grip.png
new file mode 100644
index 0000000..cae2a4e
--- /dev/null
+++ b/extras/appengine/static/resize-grip.png
Binary files differ
diff --git a/extras/appengine/static/robots.txt b/extras/appengine/static/robots.txt
new file mode 100644
index 0000000..c033917
--- /dev/null
+++ b/extras/appengine/static/robots.txt
@@ -0,0 +1,8 @@
+# Directions for web crawlers.
+# See http://www.robotstxt.org/wc/norobots.html.
+
+User-agent: HTTrack
+User-agent: puf
+User-agent: MSIECrawler
+User-agent: Nutch
+Disallow: /
diff --git a/extras/appengine/static/rpc_relay.html b/extras/appengine/static/rpc_relay.html
new file mode 100644
index 0000000..c602043
--- /dev/null
+++ b/extras/appengine/static/rpc_relay.html
@@ -0,0 +1 @@
+<html><head><script type="text/javascript" src="http://www.google.com/friendconnect/script/rpc_relay.js"></script></head></html> \ 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 %}
+<h1>About this Application</h1>
+<p>
+ This application is a online SQL formatting tool.
+</p>
+<p>
+ 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 ;-)
+</p>
+<p>
+ To get started, enter a SQL statement in the text box on the top,
+ choose some options and click on &quot;Format SQL&quot; (Ctrl+F)
+ to see the result.
+</p>
+<p>
+ <em>Note:</em> The SQL formatter and parser is in an early stage
+ of development. If you're looking for a mature tool, try one of
+ <a href="http://www.google.com/search?q=online+sql+formatter">these</a>.
+</p>
+<h2>Using it from the Command Line</h2>
+<p>
+ There are three ways to use this SQL formatter from the command line:
+</p>
+<ol>
+ <li>Grab the <a href="/source/">sources</a> and use the module in your
+ Python scripts.</li>
+ <li>
+ Write a little script in your favorite language that sends a POST
+ request to this application.<br/>
+ Read the <a href="/api/">API Documentation</a> for more information.
+ </li>
+ <li>Use
+ <a href="/static/lynx_screenshot.png"
+ alt="Lynx Screenshot" target="_blank"
+ title="Screenshot: sqlformat.appspot.com on Lynx">Lynx
+ </a>
+ </li>
+</ol>
+
+{% 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 %}
+<h1>API Documentation</h1>
+
+<p>
+ Using the API for this application is pretty simple. Just send a
+ <code>POST</code> request to
+</p>
+<p>
+ <code>http://sqlformat.appspot.com/format/</code>
+</p>
+
+<h2>Options</h2>
+<p>
+ The <code>POST</code> request accepts various options to control
+ formatting. Only the <em>data</em> option is required. All others
+ are optional.
+</p>
+
+<dl>
+ <dt>data</dt>
+ <dd>The SQL statement to format.</dd>
+ <dt>remove_comments</dt>
+ <dd>Set to 1 to remove comments.</dd>
+ <dt>keyword_case</dt>
+ <dd>How to convert keywords. Allowed values are 'lower', 'upper',
+ 'capitalize'.</dd>
+ <dt>identifier_case</dt>
+ <dd>How to convert identifiers. Allowed values are 'lower', 'upper',
+ 'capitalize'.</dd>
+ <dt>n_indents</dt>
+ <dd>An integer indicating the indendation depth.</dd>
+ <dt>right_margin</dt>
+ <dd>An integer indicating the maximum line length.</dd>
+ <dt>output_format</dt>
+ <dd>Transfer the statement into another programming language.
+ Allowed values are 'python', 'php'</dd>
+</dl>
+
+<h2>Example</h2>
+<p>
+ Here's a example in Python:
+</p>
+{% include "python-client-example.html" %}
+<p>
+ <a href="/static/sqlformat_client_example.py">Download sqlformat_example_client.py</a>
+</p>
+
+{% 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 %}
+ <a href="#output" class="skip">Jump to formatted query</a>
+{% endif %}
+
+<form method="post" action="" id="form_options" enctype="multipart/form-data">
+ <div id="input">
+ {% if form.non_field_errors %}{{form.non_field_errors}}{% endif %}
+ <div>
+ <strong>Type your SQL here:</strong><br />
+ {{form.data}}
+ {% if form.data.errors %}{{form.data.errors}}{% endif %}
+ </div>
+ <div style="margin-top: .5em;">
+ <strong>...or upload a file:</strong>
+ {{form.datafile}}
+ </div>
+ <div id="examples" style="margin-top: .5em;"></div>
+ <div id="actions" style="margin-top: .5em;">
+ <input type="submit" value="Format SQL" id="btn_format" />
+ </div>
+ {% if output %}<a name="output"></a>
+ <div id="response">{{output|safe}}</div>
+ {% else %}
+ <div id="response"></div>
+ {% endif %}
+ <div id="proc_time">
+ {% if proc_time %}Processed in {{proc_time}} seconds.{% endif %}
+ </div>
+ <div style="margin-top: 1em;">
+ <script type="text/javascript">
+ <!--
+ google_ad_client = "pub-8870624642249726";
+ /* 468x60, Erstellt 07.03.09 */
+ google_ad_slot = "9840041509";
+ google_ad_width = 468;
+ google_ad_height = 60;
+ //-->
+ </script>
+ <script type="text/javascript"
+ src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
+ </script>
+ </div>
+
+ </div>
+ <div id="options">
+ <h1 class="skip">Options</h1>
+ <fieldset><legend id="general"><strong>General Options</strong></legend>
+ <div id="general_content" class="content">
+ {{form.remove_comments}}
+ <label for="id_remove_comments">{{form.remove_comments.label}}</label>
+ <br />
+ {{form.highlight}}
+ <label for="id_highlight">{{form.highlight.label}}</label>
+ {% if form.highlight.errors %}
+ <ul class="errors">{{form.highlight.errors</ul>
+ {% endif %}
+ </div>
+ </fieldset>
+ <fieldset><legend id="kwcase">
+ <strong>Keywords &amp; Identifiers</strong></legend>
+ <div>
+ {{form.keyword_case.label}}: {{form.keyword_case}}
+ </div>
+ <div>
+ {{form.identifier_case.label}}: {{form.identifier_case}}
+ </div>
+ </fieldset>
+ <fieldset><legend id="indent"><strong>Indentation &amp; Margins</strong>
+ </legend>
+ <div id="indent_content" class="content">
+ <label for="id_n_indents">Indentation: </label>
+ {{form.n_indents}} {{form.n_indents.label}}
+ <div class="help">Empty field means leave indentation unchanged.</div>
+<!--
+ <label for="id_right_margin">Right margin: </label>
+ {{form.right_margin}} {{form.right_margin.label}}
+ <div class="help">Empty field means don't mind right margin.</div>
+-->
+ </div>
+ </fieldset>
+ <fieldset><legend id="output"><strong>Output Format</strong></legend>
+ <label for="id_output_format">Language: </label>
+ {{form.output_format}}
+ </fieldset>
+
+ <div class="dev">This software is in development.</div>
+
+ </div>
+ <div class="clearfix"></div>
+</form>
+
+<script language="javascript">
+html = '<strong>...or select an example:</strong> ';
+html = html + '<select onchange="load_example();" id="sel_example">';
+html = html + '<option value="">-- Choose Example --</option>';
+{% for ex in examples %}
+ html = html + '<option value="{{ex}}">{{ex}}</option>';
+{% endfor %}
+html = html + '</select>';
+$('#examples').html(html);
+</script>
+{% 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 @@
+<html>
+ <head>
+ <title>SQLFormat - Online SQL Formatting Service</title>
+ <meta name="keywords" content="SQL, format, parse, python, beautify" />
+ <link rel="stylesheet" href="/static/pygments.css" />
+ <link rel="stylesheet" href="/static/styles.css" />
+ <script src="http://www.google.com/jsapi"></script>
+ <script>
+ google.load("jquery", "1.2.6");
+ </script>
+ <script src="/static/hotkeys.js"></script>
+ <script type="text/javascript"
+ src="/static/jquery.textarearesizer.compressed.js"></script>
+ <script src="/static/script.js"></script>
+ </head>
+ <body>
+
+ <div id="help">
+ <p>Keyboard Shortcuts</p>
+ <p>
+ <span class="shortcut">H</span> - Show / hide this help window<br/>
+ <span class="shortcut">Ctrl+F</span> - Format SQL and display result<br/>
+ <span class="shortcut">O</span> - Show / hide options<br/>
+ <span class="shortcut">T</span> - Set focus on SQL input<br/>
+ </p>
+ </div>
+
+ <div id="header">
+ <div id="header-inner">
+ {% if user %}<img src="{{userimg}}" border="0" align="right" style="padding-top:.4em;"/>{% endif %}
+ <h1>
+ <a href="/">
+ <span class="q">S<span class="q2">Q</span>L</span>Format
+ </a>
+ </h1>
+ <div id="slogan">Online SQL formatting service</div>
+ <div id="topmenu">
+ <a href="/">Home</a>
+ |
+ <a href="/about/">About</a>
+ |
+ <a href="/source/">Source Code</a>
+ |
+ <a href="/api/">API</a>
+<!--
+ |
+ {% if user %}
+ <a href="{{logout_url}}">Sign out</a>
+ {% else %}
+ <a href="{{login_url}}">Sign in</a>
+ <span style="color: #babdb6;">with your Google account
+ to save preferred settings.</span>
+ {% endif %}
+-->
+ </div>
+ </div>
+ </div>
+
+ <div id="main">
+ <div id="main-inner">
+ {% block main %}MAIN CONTENT GOES HERE{% endblock %}
+ </div>
+ </div>
+
+ <div id="footer">
+ <div id="footer-inner">
+ <div style="float: left; font-size: .85em;">
+ <div>&copy; 2009 Andi Albrecht&nbsp;&nbsp;
+ <code>&lt;albrecht dot andi gmail&gt;</code>
+ </div>
+ <div>
+ <a href="/">Home</a>
+ |
+ <a href="/about/">About</a>
+ |
+ <a href="/source/">Source Code</a>
+ |
+ <a href="/api/">API</a>
+ |
+ <a href="http://andialbrecht.wordpress.com/">Blog</a>
+ </div>
+ </div>
+ <div style="float: right;">
+ <img src="http://code.google.com/appengine/images/appengine-silver-120x30.gif"
+ alt="Powered by Google App Engine" />
+ </div>
+ <div class="clearfix"></div>
+ </div>
+ </div>
+
+ <script type="text/javascript">
+ var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+ document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+ </script>
+ <script type="text/javascript">
+ try {
+ var pageTracker = _gat._getTracker("UA-3535525-2");
+ pageTracker._trackPageview();
+ } catch(err) {}</script>
+ <script>init();</script>
+
+ </body>
+</html>
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 @@
+<div class="highlight example"><pre><span class="c">#!/usr/bin/env python</span>
+
+<span class="k">import</span> <span class="nn">urllib</span>
+<span class="k">import</span> <span class="nn">urllib2</span>
+
+<span class="n">payload</span> <span class="o">=</span> <span class="p">(</span>
+ <span class="p">(</span><span class="s">&#39;data&#39;</span><span class="p">,</span> <span class="s">&#39;select * from foo join bar on val1 = val2 where id = 123;&#39;</span><span class="p">),</span>
+ <span class="p">(</span><span class="s">&#39;format&#39;</span><span class="p">,</span> <span class="s">&#39;text&#39;</span><span class="p">),</span>
+ <span class="p">(</span><span class="s">&#39;keyword_case&#39;</span><span class="p">,</span> <span class="s">&#39;upper&#39;</span><span class="p">),</span>
+ <span class="p">(</span><span class="s">&#39;reindent&#39;</span><span class="p">,</span> <span class="bp">True</span><span class="p">),</span>
+ <span class="p">(</span><span class="s">&#39;n_indents&#39;</span><span class="p">,</span> <span class="mf">2</span><span class="p">),</span>
+ <span class="p">)</span>
+
+<span class="n">response</span> <span class="o">=</span> <span class="n">urllib2</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="s">&#39;http://sqlformat.appspot.com/format/&#39;</span><span class="p">,</span>
+ <span class="n">urllib</span><span class="o">.</span><span class="n">urlencode</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
+<span class="k">print</span> <span class="n">response</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
+</pre></div>
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 %}
+<div id="response">
+ <h1>Source Code</h1>
+
+ <h2>Python Module</h2>
+ <p>
+ The sources for the SQL parser and formatter module are currently
+ hosted on Gitorious.
+ To clone the repository run:
+ <p>
+ <code class="pre">git clone git://github.com/andialbrecht/python-sqlparse.git</code>
+ </p>
+ <p>
+ <a href="http://python-sqlparse.googlecode.com">Visit the project page</a>
+ |
+ <a href="http://gitorious.org/projects/python-sqlparse/repos/mainline/trees/master">Browse the sources online</a>
+ </p>
+ <p>
+ Some relevant parts of the Python module contain code from the
+ <a href="http://pygments.org/">pygments</a> syntax highlighter.
+ The underlying Python module uses a non-validating SQL parser.
+ This approach makes it possible to parse even syntactically incorrect
+ SQL statements.
+ </p>
+
+ <p>
+ Currently the parser module is used by
+ <a href="http://crunchyfrog.googlecode.com/">CrunchyFrog</a> - a
+ database front-end for Gnome.
+ </p>
+
+ <p>
+ The <code>sqlparse</code> module is released under the terms of the
+ <a href="http://www.opensource.org/licenses/bsd-license.php">New BSD License</a>.
+ </p>
+
+ <h2>App Engine Application</h2>
+ <p>
+ The source code for this App Engine application is available in the
+ <code>examples</code> directory of the Python module
+ (but it's really nothing special ;-).
+ </p>
+
+ <h2>Contributing</h2>
+ <p>
+ Please file bug reports and feature requests on the project site at
+ <a href="http://code.google.com/p/python-sqlparse/issues/entry">http://code.google.com/p/python-sqlparse/issues/entry</a>
+ or if you have code to contribute upload it to
+ <a href="http://codereview.appspot.com">http://codereview.appspot.com</a>
+ and add albrecht.andi@googlemail.com as reviewer.
+ </p>
+
+</div>
+{% endblock %}