diff options
| author | Andi Albrecht <albrecht.andi@gmail.com> | 2009-04-03 21:26:42 +0200 |
|---|---|---|
| committer | Andi Albrecht <albrecht.andi@gmail.com> | 2009-04-03 21:26:42 +0200 |
| commit | 361122eb22d5681c58dac731009e4814b3dd5fa5 (patch) | |
| tree | b096496bc9c6b8febe092d0aefd56de1a4f8f4a0 /extras/appengine | |
| download | sqlparse-361122eb22d5681c58dac731009e4814b3dd5fa5.tar.gz | |
Initial import.
Diffstat (limited to 'extras/appengine')
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 Binary files differnew file mode 100644 index 0000000..bc1a6ed --- /dev/null +++ b/extras/appengine/static/bg_options.png diff --git a/extras/appengine/static/bgfieldset.png b/extras/appengine/static/bgfieldset.png Binary files differnew file mode 100644 index 0000000..4d55f4a --- /dev/null +++ b/extras/appengine/static/bgfieldset.png diff --git a/extras/appengine/static/bgfooter.png b/extras/appengine/static/bgfooter.png Binary files differnew file mode 100644 index 0000000..9ce5bdd --- /dev/null +++ b/extras/appengine/static/bgfooter.png diff --git a/extras/appengine/static/bgtop.png b/extras/appengine/static/bgtop.png Binary files differnew file mode 100644 index 0000000..a0d4709 --- /dev/null +++ b/extras/appengine/static/bgtop.png diff --git a/extras/appengine/static/blank.gif b/extras/appengine/static/blank.gif Binary files differnew file mode 100644 index 0000000..3be2119 --- /dev/null +++ b/extras/appengine/static/blank.gif 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();"> + ‹‹ 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 Binary files differnew file mode 100644 index 0000000..6465823 --- /dev/null +++ b/extras/appengine/static/img_loading.gif 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 Binary files differnew file mode 100644 index 0000000..a879bed --- /dev/null +++ b/extras/appengine/static/loading.gif diff --git a/extras/appengine/static/lynx_screenshot.png b/extras/appengine/static/lynx_screenshot.png Binary files differnew file mode 100644 index 0000000..d1592ac --- /dev/null +++ b/extras/appengine/static/lynx_screenshot.png 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 Binary files differnew file mode 100644 index 0000000..cae2a4e --- /dev/null +++ b/extras/appengine/static/resize-grip.png 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 "Format SQL" (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 & 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 & 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>© 2009 Andi Albrecht + <code><albrecht dot andi gmail></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">'data'</span><span class="p">,</span> <span class="s">'select * from foo join bar on val1 = val2 where id = 123;'</span><span class="p">),</span> + <span class="p">(</span><span class="s">'format'</span><span class="p">,</span> <span class="s">'text'</span><span class="p">),</span> + <span class="p">(</span><span class="s">'keyword_case'</span><span class="p">,</span> <span class="s">'upper'</span><span class="p">),</span> + <span class="p">(</span><span class="s">'reindent'</span><span class="p">,</span> <span class="bp">True</span><span class="p">),</span> + <span class="p">(</span><span class="s">'n_indents'</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">'http://sqlformat.appspot.com/format/'</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 %} |
