Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
    * Prototype command line interface entry point.
    * Use SQLParse package for parsing SQL.
  • Loading branch information
andreypopp committed Mar 20, 2011
0 parents commit ebd5a9c
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
30 changes: 30 additions & 0 deletions DESIGN
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
iPSQL design
============

This document specifies concept of intelligent PostgreSQL shell.

Planned features
----------------

List of planned features:

* Vi-mode and Emacs-mode line editing facilities.

* Syntax highlighting.

* Contextual completion.

* Own parser for SQL with fallback to PostgreSQL parser (allow better
experiments with language).

* Better interface with pagers (search by columns, maybe).

* Better interface with editors (streamline completion).

* Alternative query syntax (HTSQL maybe?).

* Async operations.

* Configurable prompt.

* Possible macro support (loops, conditionals).
Empty file added ipsql/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions ipsql/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
""" Command line interface entry point."""

import getpass
import optparse

import psycopg2

from ipsql.shell import Shell

def get_parser():
parser = optparse.OptionParser(add_help_option=False)
parser.add_option(
"-c", "--command",
help="run only single command (SQL or internal) and exit")
parser.add_option(
"-h", "--host",
help="database server host or socket directory")
parser.add_option(
"-p", "--port",
help="database server port")
parser.add_option(
"-d", "--dbname",
help="database name to connect to")
parser.add_option(
"-U", "--username",
help="database user name")
return parser

def main():
parser = get_parser()
options, args = parser.parse_args()

# TODO: Better handling data type conversion errors.
host = options.host if options.host else "localhost"
port = int(options.port) if options.port else 5432
username = options.username if options.username else getpass.getuser()
dbname = options.dbname if options.dbname else username

connection = psycopg2.connect(
database=dbname,
user=username,
host=host,
port=port)

shell = Shell(connection)
shell.run()
70 changes: 70 additions & 0 deletions ipsql/shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
""" Shell."""

import sys
import copy

import sqlparse
from rl import completer as rlcompleter
from rl import completion as rlcompletion

__all__ = ["Shell"]

class Shell(object):

def __init__(self, connection, prompt="#"):
self.connection = connection
self.prompt = prompt
self._buffer = _Buffer()

def run(self):

rlcompleter.parse_and_bind("TAB: complete")
rlcompleter.completer = self.complete

while True:
try:
self._buffer.add_line(raw_input(self.prompt + " "))
except (EOFError, KeyboardInterrupt), e:
break

self.exit()

def exit(self):
# TODO: Better move that to parametrized callback.
sys.exit(0)

def complete(self, _text, _state):
buffer = self._buffer.copy()
buffer.add_line(rlcompletion.line_buffer)

class _Buffer(object):

def __init__(self, statements=None, lines=None, current_statement=None):
self.statements = statements or []
self.lines = lines or []
self.current_statement = current_statement or None

def add_statement(self, line):
self.statements.append(sqlparse.parse(line))

def add_line(self, line):
splitted = sqlparse.split(line)
for line in splitted:
lines = self.lines + [line]
if line.strip().endswith(";"):
self.statements.append(sqlparse.parse("\n".join(lines))[0])
self.lines = []
self.current_statement = None
else:
self.lines = lines
self.current_statement = sqlparse.parse("\n".join(lines))[0]

@property
def is_ready_for_exec(self):
return bool(self.statements)

def copy(self):
statements = copy.deepcopy(self.statements)
lines = copy.deepcopy(self.lines)
current_statement = copy.deepcopy(self.current_statement)
return _Buffer(statements, lines, current_statement)
25 changes: 25 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from setuptools import setup, find_packages
import sys, os

version = "0.1"

setup(
name="ipsql",
version=version,
description="Intelligent PostgreSQL shell.",
author="Andrey Popp",
author_email="8mayday@gmail.com",
license="3BSD",
packages=find_packages(exclude=["ez_setup", "examples", "tests"]),
include_package_data=True,
zip_safe=False,
install_requires=[
"psycopg2",
"rl",
"sqlparse"
],
entry_points="""
[console_scripts]
ipsql = ipsql.cli:main
""",
)

0 comments on commit ebd5a9c

Please sign in to comment.