cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync#!/usr/bin/env python
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync# -*- coding: utf-8 -*-
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync# $Id$
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync"""
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncCopyright (C) 2012-2014 Oracle Corporation
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncThis file is part of VirtualBox Open Source Edition (OSE), as
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncavailable from http://www.virtualbox.org. This file is free software;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncyou can redistribute it and/or modify it under the terms of the GNU
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncGeneral Public License (GPL) as published by the Free Software
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncFoundation, in version 2 as it comes in the "COPYING" file of the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncVirtualBox OSE distribution. VirtualBox OSE is distributed in the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsynchope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncThe contents of this file may alternatively be used under the terms
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncof the Common Development and Distribution License Version 1.0
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync(CDDL) only, as it comes in the "COPYING.CDDL" file of the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncVirtualBox OSE distribution, in which case the provisions of the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncCDDL are applicable instead of those of the GPL.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncYou may elect to license modified versions of this file under the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncterms and conditions of either the GPL or the CDDL or both.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync"""
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync"""
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncConverts doxygen style comments in SQL script to COMMENT ON statements.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync"""
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncimport sys;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncimport re;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncdef errorMsg(sMsg):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sys.stderr.write('error: %s\n' % (sMsg,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return 1;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncclass SqlDox(object):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync """
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Class for parsing relevant comments out of a pgsql file
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync and emit COMMENT ON statements from it.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync """
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def __init__(self, oFile, sFilename):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.oFile = oFile;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sFilename = sFilename;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.iLine = 0; # The current input line number.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sComment = None; # The current comment.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.fCommentComplete = False; # Indicates that the comment has ended.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sCommentSqlObj = None; # SQL object indicated by the comment (@table).
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sOuterSqlObj = None; # Like 'table yyyy' or 'type zzzz'.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sPrevSqlObj = None; # Like 'table xxxx'.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def error(self, sMsg):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return errorMsg('%s(%d): %s' % (self.sFilename, self.iLine, sMsg,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def dprint(self, sMsg):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sys.stderr.write('debug: %s\n' % (sMsg,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return True;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def resetComment(self):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sComment = None;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.fCommentComplete = False;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sCommentSqlObj = None;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def quoteSqlString(self, s):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return s.replace("'", "''");
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def commitComment2(self, sSqlObj):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if self.sComment is not None and sSqlObj is not None:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync print("COMMENT ON %s IS\n '%s';\n\n" % (sSqlObj, self.quoteSqlString(self.sComment.strip())));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.resetComment();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return True;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def commitComment(self):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return self.commitComment2(self.sCommentSqlObj);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def process(self):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync for sLine in self.oFile:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.iLine += 1;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sLine = sLine.strip();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.dprint('line %d: %s\n' % (self.iLine, sLine));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if sLine.startswith('--'):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if sLine.startswith('--- '):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # New comment.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # The first list may have a @table, @type or similar that we're interested in.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.commitComment();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sLine = sLine.lstrip('- ');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if sLine.startswith('@table '):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sCommentSqlObj = 'TABLE ' + (sLine[7:]).rstrip();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sComment = '';
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync elif sLine.startswith('@type '):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sCommentSqlObj = 'TYPE ' + (sLine[6:]).rstrip();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sComment = '';
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync elif sLine.startswith('@todo') \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync or sLine.startswith('@file') \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync or sLine.startswith('@page') \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync or sLine.startswith('@name') \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync or sLine.startswith('@{') \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync or sLine.startswith('@}'):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Ignore.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync pass;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync elif sLine.startswith('@'):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return self.error('Unknown tag: %s' % (sLine,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync else:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sComment = sLine;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync elif (sLine.startswith('-- ') or sLine == '--') \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync and self.sComment is not None and self.fCommentComplete is False:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Append line to comment.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if sLine == '--':
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sLine = '';
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync else:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sLine = (sLine[3:]);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if self.sComment == '':
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sComment = sLine;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync else:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sComment += "\n" + sLine;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync elif sLine.startswith('--< '):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Comment that starts on the same line as the object it describes.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sLine = (sLine[4:]).rstrip();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # => Later/never.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync else:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Not a comment that interests us. So, complete any open
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # comment and commit it if we know which SQL object it
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # applies to.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.fCommentComplete = True;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if self.sCommentSqlObj is not None:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.commitComment();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync else:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Not a comment. As above, we complete and optionally commit
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # any open comment.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.fCommentComplete = True;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if self.sCommentSqlObj is not None:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.commitComment();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Check for SQL (very fuzzy and bad).
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync asWords = sLine.split(' ');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if len(asWords) >= 3 \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync and asWords[0] == 'CREATE':
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # CREATE statement.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sType = asWords[1];
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sName = asWords[2];
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if sType == 'UNIQUE' and sName == 'INDEX' and len(asWords) >= 4:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sType = asWords[2];
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sName = asWords[3];
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if sType in ('TABLE', 'TYPE', 'INDEX', 'VIEW'):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sOuterSqlObj = sType + ' ' + sName;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sPrevSqlObj = self.sOuterSqlObj;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.dprint('%s' % (self.sOuterSqlObj,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.commitComment2(self.sOuterSqlObj);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync elif len(asWords) >= 1 \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync and self.sOuterSqlObj is not None \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync and self.sOuterSqlObj.startswith('TABLE ') \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync and re.search("^(as|al|bm|c|enm|f|i|l|s|ts|uid|uuid)[A-Z][a-zA-Z0-9]*$", asWords[0]) is not None:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Possibly a column name.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sPrevSqlObj = 'COLUMN ' + self.sOuterSqlObj[6:] + '.' + asWords[0];
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.dprint('column? %s' % (self.sPrevSqlObj));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.commitComment2(self.sPrevSqlObj);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Check for semicolon.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync #
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if sLine.find(");") >= 0:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.sOuterSqlObj = None;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return 0;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncdef usage():
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sys.stderr.write('usage: gen-sql-comments.py <filename.pgsql>\n'
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync '\n'
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync 'The output goes to stdout.\n');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return 0;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncdef main(asArgs):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Parse the argument. :-)
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sInput = None;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if (len(asArgs) != 2):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sys.stderr.write('syntax error: expected exactly 1 argument, a psql file\n');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync usage();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return 2;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sInput = asArgs[1];
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Do the job, outputting to standard output.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync try:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync oFile = open(sInput, 'r');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync except:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return errorMsg("failed to open '%s' for reading" % (sInput,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync me = SqlDox(oFile, sInput);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return me.process();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncsys.exit(main(sys.argv));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync