def searchRows(self, keyColumn, key):
     """
         Searches rows that have the same string value in 'keyColumn' as 'key'.
     Returns a CSVFileEngine object, representing search results.
         'keyColumn' - zero-based column index
         'key' - string-convertable object to search
     """
     keyColumn = self._validateColumn(keyColumn)
     orderFile = self._createOrderFileIfNeeded(keyColumn)
     orderFile.setPosition(self._searchFieldPositionInOrderFile(keyColumn, key))
     result = []
     while not orderFile.endOfFile():
         rowIndex = orderFile.readInteger()
         rowData = self.rowData(rowIndex)
         if rowData[keyColumn + 1] == key:
             result.append(rowData)
         else:
             break
     temporaryFile = AutoResizeableFile()
     string = ""
     for i in xrange(0, self._columnsCount):
         string += self._titles[i]
         if i != self._columnsCount - 1:
             string += ";"
     temporaryFile.writeLine(string)
     result.sort(lambda x, y: x[0] - y[0])
     for item in result:
         string = ""
         for i in xrange(1, len(item)):
             string += item[i]
             if i != len(item) - 1:
                 string += ";"
         temporaryFile.writeLine(string)
     return CSVFileEngine(temporaryFile)
class CSVFileEngine_text(unittest.TestCase):
    def setUp(self):
        self._file = AutoResizeableFile()
        self._file.writeLine("T1;T02;T003")
        self._engine = CSVFileEngine(self._file)


    def tearDown(self):
        del self._file
        del self._engine


    def testEngineOperations(self):
        self.assertEqual(self._engine.columnCount(), 3, ".columnCount() return error")
        self.assertEqual(self._engine.rowCount(), 0, ".rowCount() return error")
        self.assertEqual(tuple(self._engine.titles()), ("T1", "T02", "T003"), ".titles() return error")
        self._engine.insertRow(0, ("a", "aaa", "aa"))
        self._engine.insertRow(1, ("bb", "aaa", "bbb"))
        self._engine.insertRow(0, ("ccc", "c", "bbb"))
        self._engine.insertRow(3, ("dddd", "eeeee", "aa"))
        self.assertEqual(self._engine.rowData(0), (0, "ccc", "c", "bbb"), ".rowData() return error")
        self.assertEqual(self._engine.rowData(1), (1, "a", "aaa", "aa"), ".rowData() return error")
        self.assertEqual(self._engine.rowData(2), (2, "bb", "aaa", "bbb"), ".rowData() return error")
        self.assertEqual(self._engine.rowData(3), (3, "dddd", "eeeee", "aa"), ".rowData() return error")
        self.assertEqual(self._engine.rowCount(), 4, ".rowCount() return error")
        self._engine.removeRowAction(1)
        self._engine.removeRowAction(2)
        self.assertEqual(self._engine.rowCount(), 2, ".rowCount() return error")
        self.assertEqual(self._engine.rowData(0), (0, "ccc", "c", "bbb"), ".rowData() return error")
        self.assertEqual(self._engine.rowData(1), (1, "bb", "aaa", "bbb"), ".rowData() return error")
        searchResult = self._engine.searchRows(2, "bbb")
        self.assertEqual(searchResult.rowCount(), 2, "Search result length error")
        self._engine.insertRow(0, ("a", "aaa", ""))
        self._engine.insertRow(1, ("bb", "aaa", "bbb"))
        self._engine.insertRow(2, ("ccc", "c", "bbb"))
        self._engine.insertRow(3, ("dddd", "eeeee", "aa"))
        self.assertEqual(self._engine.rowCount(), 6, ".rowCount() return error")
        self._engine.changeFieldData(0, 2, "CHANGED")
        self._engine.changeFieldData(2, 2, "0")
        self._engine.changeFieldData(5, 2, "GO")
        self._engine.changeFieldData(4, 0, "")
        self.assertEqual(self._engine.fieldData(0, 2), "CHANGED", ".fieldData() return error")
        self.assertEqual(self._engine.fieldData(2, 2), "0", ".fieldData() return error")
        self.assertEqual(self._engine.fieldData(5, 2), "GO", ".fieldData() return error")
        self.assertEqual(self._engine.fieldData(4, 0), "", ".fieldData() return error")
        searchResult = self._engine.searchRows(1, "aaa")
        self.assertEqual(searchResult.rowCount(), 3, "Search result length error")
        self._engine.insertRow(0, ("", "", ""))
        self._engine.changeFieldData(0, 0, "HI")
        self._engine.changeFieldData(0, 1, "HELLO")
        self._engine.changeFieldData(0, 2, "BYE")
        self.assertEqual(tuple(self._engine.rowData(0)), (0, "HI", "HELLO", "BYE"), ".rowData() return error")
 def setUp(self):
     self._file = AutoResizeableFile()
     self._file.writeLine("staand-081194:add, remove, modify")
     self._file.writeLine("chess_master-chess:add, modify")
     self._file.writeLine("stamax-314159:")
     self._file.writeLine("dsz-dsz:remove")
     self._file.flush()
 def __init__(self, resizeableFile):
     """
         Default constructor.
         'resizeableFile' - reference to AutoResizeableFile object with needed *.CSV
     """
     FilePackingListener.__init__(self)
     self._file = resizeableFile
     self._fieldsIndexingFile = AutoResizeableFile()
     oldPosition = self._file.position()
     self._file.setPosition(0)
     self._titles = self._file.readLine().split(";")
     self._columnsCount = len(self._titles)
     self._rowsCount = 0
     while not self._file.endOfFile():
         left = self._file.position()
         string = self._file.readLine()
         if len(string) != 0:
             self._rowsCount += 1
             indexes = self._stringToFieldsIndexes(string, left)
             for index in indexes:
                 self._fieldsIndexingFile.writeInteger(index)
     self._orderIndexesFiles = []
     for i in xrange(0, self._columnsCount):
         self._orderIndexesFiles.append(None)
     self._file.addPackingListener(self)
     self._file.setPosition(oldPosition)
class UsersList_test(unittest.TestCase):
    def setUp(self):
        self._file = AutoResizeableFile()
        self._file.writeLine("staand-081194:add, remove, modify")
        self._file.writeLine("chess_master-chess:add, modify")
        self._file.writeLine("stamax-314159:")
        self._file.writeLine("dsz-dsz:remove")
        self._file.flush()
        
        
    def tearDown(self):
        self._file.close()


    def testParsing(self):
        users = UsersList(self._file.path())
        staand = users.get("staand")
        stamax = users.get("stamax")
        chess_master = users.get("chess_master")
        dsz = users.get("dsz")
        none = users.get("any_other")
        self.assertNotEqual(staand, None, "'staand' user is 'None'")
        self.assertNotEqual(stamax, None, "'stamax' user is 'None'")
        self.assertNotEqual(chess_master, None, "'chess_master' user is 'None'")
        self.assertNotEqual(dsz, None, "'dsz' user is 'None'")
        self.assertEqual(none, None, "'none' user is NOT 'None'")
        self.assertTrue(staand.login() == "staand" and staand.password() == "081194", "'staand' attributes error")
        self.assertTrue(chess_master.login() == "chess_master" and chess_master.password() == "chess", "'chess_master' attributes error")
        self.assertTrue(stamax.login() == "stamax" and stamax.password() == "314159", "'stamax' attributes error")
        self.assertTrue(dsz.login() == "dsz" and dsz.password() == "dsz", "'dsz' attributes error")
        rights = staand.rights()
        self.assertTrue(rights.fieldsModifyingPermited() and rights.rowsAddingPermited() and rights.rowsRemovingPermited(), 
                        "'staand' rights error")
        rights = stamax.rights()
        self.assertFalse(rights.fieldsModifyingPermited() or rights.rowsAddingPermited() or rights.rowsRemovingPermited(), 
                        "'stamax' rights error")
        rights = chess_master.rights()
        self.assertTrue(rights.fieldsModifyingPermited() and rights.rowsAddingPermited() and not rights.rowsRemovingPermited(), 
                        "'chess_master' rights error")
        rights = dsz.rights()
        self.assertTrue(not rights.fieldsModifyingPermited() and not rights.rowsAddingPermited() and rights.rowsRemovingPermited(), 
                        "'dsz' rights error")
 def setUp(self):
     self._file = AutoResizeableFile()
     self._file.writeLine("T1;T02;T003")
     self._engine = CSVFileEngine(self._file)
class CSVFileEngine(FilePackingListener):
    """
        *.CSV files engine class
    """

    def __init__(self, resizeableFile):
        """
            Default constructor.
            'resizeableFile' - reference to AutoResizeableFile object with needed *.CSV
        """
        FilePackingListener.__init__(self)
        self._file = resizeableFile
        self._fieldsIndexingFile = AutoResizeableFile()
        oldPosition = self._file.position()
        self._file.setPosition(0)
        self._titles = self._file.readLine().split(";")
        self._columnsCount = len(self._titles)
        self._rowsCount = 0
        while not self._file.endOfFile():
            left = self._file.position()
            string = self._file.readLine()
            if len(string) != 0:
                self._rowsCount += 1
                indexes = self._stringToFieldsIndexes(string, left)
                for index in indexes:
                    self._fieldsIndexingFile.writeInteger(index)
        self._orderIndexesFiles = []
        for i in xrange(0, self._columnsCount):
            self._orderIndexesFiles.append(None)
        self._file.addPackingListener(self)
        self._file.setPosition(oldPosition)

    def flush(self):
        """
            Flushes all changes to disk
        """
        self._file.flush()

    def filePackedEvent(self, packingInfoList):
        """
            Reimplemented from FilePackingListener class
        """
        oldPosition = self._fieldsIndexingFile.position()
        self._fieldsIndexingFile.pack()
        for position in xrange(0, self._fieldsIndexingFile.size(), 4):
            self._fieldsIndexingFile.setPosition(position)
            currentValue = self._fieldsIndexingFile.readInteger()
            for item in packingInfoList:
                oldRange = item.oldRange()
                if oldRange[0] <= currentValue <= oldRange[1]:
                    self._fieldsIndexingFile.setPosition(position)
                    self._fieldsIndexingFile.writeInteger(currentValue + item.shiftingValue(), True)
        self._fieldsIndexingFile.setPosition(oldPosition)

    def rowCount(self):
        """
            Returns amount of rows in the table
        """
        return self._rowsCount

    def columnCount(self):
        """
            Returns amount of rows in the table
        """
        return self._columnsCount

    def titles(self):
        """
            Returns a list of columns titles
        """
        return self._titles

    def rowData(self, row):
        """
            Returns a tuple representing each field in the specified row.
        The first element of tuple is row index (zero-based)
            'row' - zero-based row index
        """
        row = self._validateRow(row)
        fieldsRanges = self._getRowFieldsRanges(row)
        data = self._readStrings(fieldsRanges)
        return tuple([row] + data)

    def fieldData(self, row, column):
        """
            Returns a string representation of the specified field.
            'row' - zero-based row index
            'column' - zero-based column index
        """
        row = self._validateRow(row)
        column = self._validateColumn(column)
        fieldRange = self._getRowFieldsRanges(row)[column]
        return self._readStrings([fieldRange])[0]

    def changeFieldData(self, row, column, data):
        """
            Changes specified field data. Returns None
            'row' - zero-based row index
            'column' - zero-based column index
            'data' - string-convertable object
        """
        row = self._validateRow(row)
        column = self._validateColumn(column)
        self._removeFieldFromOrderFileIfPossible(row, column)
        rowFieldsRanges = self._getRowFieldsRanges(row)
        fieldRange = rowFieldsRanges[column]
        rowBeginIndex = rowFieldsRanges[0][0]
        oldPosition = self._file.position()
        self._file.setPosition(fieldRange[0])
        self._file.write(data, fieldRange[1] - fieldRange[0])
        self._file.setPosition(oldPosition)
        self._fieldsIndexingFile.setPosition(
            self._fieldsIndexingFile.realPositionFor(4 * row * (self._columnsCount + 1))
        )
        self._fieldsIndexingFile.writeInteger(rowBeginIndex, True)
        self._insertFieldIntoOrderFileIfPossible(row, column, data, self._rowsCount - 1)

    def insertRow(self, rowIndex, rowData):
        """
            Inserts a new row in the specified position. Returns None.
            'rowIndex' - zero-based row index. New row will have this index
        """
        rowIndex = self._validateRow(rowIndex, self._rowsCount + 1)
        try:
            data = []
            for i in xrange(0, self._columnsCount):
                data.append(str(rowData[i]))
            rowData = data
        except (ValueError, TypeError, AttributeError, IndexError):
            raise ValueError(
                "rowData must be a columnCount-size sequence of string-convertable values",
                {"rowData": rowData, "raisingObject": self},
            )
        for i in xrange(0, self._columnsCount):
            self._insertFieldIntoOrderFileIfPossible(rowIndex, i, rowData[i])
        string = ""
        oldPosition = self._file.position()
        for i in xrange(0, self._columnsCount):
            string = string + rowData[i]
            if i != self._columnsCount - 1:
                string += ";"
        insertPosition = self._file.size()
        if rowIndex != self._rowsCount:
            insertPosition = self._getRowFieldsRanges(rowIndex)[0][0]
        indexes = self._stringToFieldsIndexes(string, insertPosition)
        self._file.setPosition(insertPosition)
        self._file.writeLine(string)
        self._fieldsIndexingFile.setPosition(self._fieldsIndexingFile.size())
        if rowIndex != self._rowsCount:
            self._fieldsIndexingFile.setPosition(
                self._fieldsIndexingFile.realPositionFor(4 * rowIndex * (self._columnsCount + 1))
            )
        for index in indexes:
            self._fieldsIndexingFile.writeInteger(index)
        self._file.setPosition(oldPosition)
        self._rowsCount = self._rowsCount + 1

    def removeRow(self, row):
        """
            Removes row in the specified position. Returns None.
            'rowIndex' - zero-based removing row index
        """
        row = self._validateRow(row)
        for i in xrange(0, self._columnsCount):
            self._removeFieldFromOrderFileIfPossible(row, i)
        indexFileLeft = self._fieldsIndexingFile.realPositionFor(row * 4 * (self._columnsCount + 1))
        indexFileRight = indexFileLeft + 4 * (self._columnsCount + 1)
        fileLeft, fileRight = self._getRowFieldsRanges(row)[0][0], 0
        if row == self._rowsCount - 1:
            fileRight = self._file.size()
        else:
            fileRight = self._getRowFieldsRanges(row + 1)[0][0]
        self._fieldsIndexingFile.removeRange(indexFileLeft, indexFileRight)
        self._file.removeRange(fileLeft, fileRight)
        self._rowsCount = self._rowsCount - 1

    def searchRows(self, keyColumn, key):
        """
            Searches rows that have the same string value in 'keyColumn' as 'key'.
        Returns a CSVFileEngine object, representing search results.
            'keyColumn' - zero-based column index
            'key' - string-convertable object to search
        """
        keyColumn = self._validateColumn(keyColumn)
        orderFile = self._createOrderFileIfNeeded(keyColumn)
        orderFile.setPosition(self._searchFieldPositionInOrderFile(keyColumn, key))
        result = []
        while not orderFile.endOfFile():
            rowIndex = orderFile.readInteger()
            rowData = self.rowData(rowIndex)
            if rowData[keyColumn + 1] == key:
                result.append(rowData)
            else:
                break
        temporaryFile = AutoResizeableFile()
        string = ""
        for i in xrange(0, self._columnsCount):
            string += self._titles[i]
            if i != self._columnsCount - 1:
                string += ";"
        temporaryFile.writeLine(string)
        result.sort(lambda x, y: x[0] - y[0])
        for item in result:
            string = ""
            for i in xrange(1, len(item)):
                string += item[i]
                if i != len(item) - 1:
                    string += ";"
            temporaryFile.writeLine(string)
        return CSVFileEngine(temporaryFile)

    def fieldsIndexes(self):
        """
            Method was added for debugging.
        Returns a list of tuples of fields indexes in the real file. 
        """
        result = []
        self._fieldsIndexingFile.pack()
        self._fieldsIndexingFile.setPosition(0)
        for i in xrange(0, self._rowsCount):
            rowIndexes = []
            for j in xrange(0, self._columnsCount):
                rowIndexes.append(self._fieldsIndexingFile.readInteger())
            result.append(tuple(rowIndexes))
        return result

    def _createOrderFileIfNeeded(self, column):
        if self._orderIndexesFiles[column] != None:
            return self._orderIndexesFiles[column]
        self._orderIndexesFiles[column] = AutoResizeableFile()
        for i in xrange(0, self._rowsCount):
            self._insertFieldIntoOrderFileIfPossible(i, column, self.fieldData(i, column), i)
        return self._orderIndexesFiles[column]

    def _removeFieldFromOrderFileIfPossible(self, row, column, rowsCount=-1):
        if self._orderIndexesFiles[column] == None:
            return
        if rowsCount == -1:
            rowsCount = self._rowsCount
        orderFile = self._orderIndexesFiles[column]
        oldPosition = orderFile.position()
        removingPosition = -1
        for i in xrange(0, rowsCount):
            realPosition = orderFile.realPositionFor(i * 4)
            orderFile.setPosition(realPosition)
            readValue = orderFile.readInteger()
            if readValue == row:
                removingPosition = realPosition
            elif readValue > row:
                orderFile.setPosition(realPosition)
                orderFile.writeInteger(readValue - 1, True)
        if removingPosition != -1:
            orderFile.removeRange(removingPosition, removingPosition + 4)
        orderFile.setPosition(oldPosition)

    def _insertFieldIntoOrderFileIfPossible(self, row, column, data, rowsCount=-1):
        if self._orderIndexesFiles[column] == None:
            return
        if rowsCount == -1:
            rowsCount = self._rowsCount
        orderFile = self._orderIndexesFiles[column]
        oldPosition = orderFile.position()
        insertPositon = self._searchFieldPositionInOrderFile(column, data, rowsCount)
        for i in xrange(0, rowsCount):
            realPosition = orderFile.realPositionFor(i * 4)
            orderFile.setPosition(realPosition)
            readValue = orderFile.readInteger()
            if readValue >= row:
                orderFile.setPosition(realPosition)
                orderFile.writeInteger(readValue + 1, True)
        orderFile.setPosition(insertPositon)
        orderFile.writeInteger(row)
        orderFile.setPosition(oldPosition)

    def _searchFieldPositionInOrderFile(self, column, data, rowsCount=-1):
        if rowsCount == -1:
            rowsCount = self._rowsCount
        if rowsCount == 0:
            return 0
        orderFile = self._orderIndexesFiles[column]
        left, right = 0, rowsCount
        while left < right:
            middle = (left + right) / 2
            orderFile.setPosition(orderFile.realPositionFor(middle * 4))
            middleRowIndex = orderFile.readInteger()
            middleData = self.fieldData(middleRowIndex, column)
            if data <= middleData:
                right = middle
            else:
                left = middle + 1
        return orderFile.realPositionFor(right * 4)

    def _stringToFieldsIndexes(self, string, left):
        indexes = [left]
        fieldsCount = 0
        for stringIndex in xrange(0, len(string)):
            if string[stringIndex] == ";":
                fieldsCount += 1
                indexes.append(stringIndex + left)
                if fieldsCount == self._columnsCount - 1:
                    break
        indexes.append(left + len(string))
        return indexes

    def _getRowFieldsRanges(self, row):
        realPosition = self._fieldsIndexingFile.realPositionFor(4 * row * (self._columnsCount + 1))
        oldPosition = self._fieldsIndexingFile.position()
        self._fieldsIndexingFile.setPosition(realPosition)
        indexes = []
        for i in xrange(0, self._columnsCount + 1):
            indexes.append(self._fieldsIndexingFile.readInteger())
        result = []
        for i in xrange(0, len(indexes) - 1):
            if i == 0:
                result.append((indexes[i], indexes[i + 1]))
            else:
                result.append((indexes[i] + 1, indexes[i + 1]))
        self._fieldsIndexingFile.setPosition(oldPosition)
        return result

    def _readStrings(self, rangesList):
        oldPosition = self._file.position()
        result = []
        for item in rangesList:
            self._file.setPosition(item[0])
            result.append(self._file.read(item[1] - item[0]))
        self._file.setPosition(oldPosition)
        return result

    def _validateInteger(self, value):
        try:
            return int(value)
        except (TypeError, AttributeError, ValueError):
            raise TypeError("Value mast be an integer", {"raisingObject": self, "value": value})

    def _validateRow(self, row, limit=-1):
        if limit == -1:
            limit = self._rowsCount
        try:
            row = self._validateInteger(row)
        except TypeError, exception:
            exception.message = "Row index must be an integer"
            raise exception
        if row >= limit:
            raise IndexError("Row index is out-of-range", {"raisingObject": self, "index": row})
        return row