Employee Information Query System

#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
#
# Author:   Payne Zheng <[email protected]>
# Date:     2018-04-19 16:41:15
# Location: DongGuang
# Desc:     staff information add/delete/change/search
#


import them
import platform
import hashlib
from tabulate import tabulate

STAFF_DB = 'user_db'
DB_COLUMN = ['id', 'name', 'age', 'phone', 'dept', 'in_data']

def Help(help_cmd):
    """
    help information
    :param help_cmd:
    :return:
    """
    help_msg = """
    help add the following parameters to view the related statement help information
        find or -f : query statement
        del or -d : delete statement
        add or -a : add statement
        update or -u : update statement
    exit or q : Exit the program (Note: If you do not exit in this way, the modification of the data will not take effect)
    """
    help_find_msg = """
    >>>Can perform fuzzy query, support the following query syntax, support [=,>,<,>=,<=,like] judgment syntax:
        find name,age from staff_table where age > 22
        find * from staff_table where dept = "IT"
        find * from staff_table where enroll_date like "2013"
    """
    help_add_msg = """
    >>>You can add new records, use phone as the unique key, and the staff_id is incremented automatically
        add staff_table Alex Li,25,134435344,IT,2015‐10‐29
    """
    help_del_msg = """
    >>>Can delete specified condition records
        del from staff where id=3
    """
    help_update_msg = """
    >>>You can modify employee information, the syntax is as follows:
        update staff_table set dept="Market" where dept = "IT"
        update staff_table set age=25 where name = "Alex Li"
    """

    if help_cmd.strip() in ['help', 'h', 'H']:
        Log(help_msg, 'info')
    elif help_cmd.strip() in ['help find', 'help -f']:
        Log(help_find_msg, 'info')
    elif help_cmd.strip() in ['help add', 'help -a']:
        Log(help_add_msg, 'info')
    elif help_cmd.strip() in ['help del', 'help -d']:
        Log(help_del_msg, 'info')
    elif help_cmd.strip() in ['help update', 'help -u']:
        Log(help_update_msg, 'info')
    else:
        Log('No such option', 'error')
        return False


def Log(msg, msg_type):
    """
    output log
    :param msg_type: eg. error|info
    :param msg: log message
    :return:
    """
    if msg_type == 'error':
        print('\033[1;31m[ERROR]: {}\033[0m'.format(msg))
    else:
        print('\033[1;34m[INFO]: {}\033[0m'.format(msg))


def LoadStaffDB(file):
    """
    Load user information into memory
    :param file: STAFF_DB
    :return: staff_data
    """
    staff_data = {
        'id': [],
        'name': [],
        'age': [],
        'phone': [],
        'dept': [],
        'in_data': []
    }

    with open(file, 'r', encoding='utf-8') as f:
        for line in f:
            staff_id, name, age, phone, dept, in_data = line.strip().split(',')
            staff_data['id'].append(staff_id)
            staff_data['name'].append(name)
            staff_data['age'].append(age)
            staff_data['phone'].append(phone)
            staff_data['dept'].append(dept)
            staff_data['in_data'].append(in_data)

    return staff_data


def SaveStaffDB(file, save_staff_data):
    """
    Write the modified data to the file database
    :param file: the file to be stored in
    :param save_staff_data: the data table to archive
    :return:
    """
    dict_to_list = []
    new_file = '{}.new'.format(file)

    # Check if the employee table has been modified, and save the operation only if there is a modification
    STAFF_DATA_MD5_B = CheckMd5(STAFF_DATA)
    if STAFF_DATA_MD5_B == STAFF_DATA_MD5_A:
        Log("### You haven't made any changes, with no needs for save it", 'info')
        return None

    # Convert the data dictionary to a list (remove the key of the dictionary)
    for k in DB_COLUMN: # When doing list conversion, you must use the fields in DB_COLUMN to correspond
        dict_to_list.append(save_staff_data[k])

    # Convert to a list of one row of employees
    new_list = DisplayDataTransform(dict_to_list)

    # Take out the sublist of each employee and convert it into a string, and then store it in the file
    with open(new_file, 'w', encoding='utf-8') as save_f:
        for i in new_list:
            save_line = ','.join(i)
            save_f.write(save_line + '\n')

    # After archiving, rename the new library file to the old library name user_db.new => user_db
    if platform.system() == 'Windows': # You cannot overwrite an existing file with rename on windows
        os.remove(file)
    try:
        os.rename(new_file, file)
    except OSError as e:
        Log(e, 'error')


def MakeTempDict():
    """
    Create an initial dictionary containing all columns to store dirty data matching each where condition
    :return: dictionary containing key
    """
    temp_dict = {}
    for i in DB_COLUMN:
        temp_dict[i] = []
    return temp_dict


def DisplayDataTransform(init_data):
    """
    Convert the final matching data format so that it can be displayed with tabulate
    :param init_data: eg. [[1,3,4,5],[22,33,44,55],['zayne','wayne','payne','rain']]
    :return: show_data eg. [[1,22,'zayne'],[3,33,'wayne'],[4,44,'payne'],[5,55,'rain']]
    """
    show_data = []
    if len(init_data) > 0 and len(init_data[0]) > 0:
        for i in range(len(init_data[0])):
            s = [init_data[k][i] for k in range(len(init_data))]
            show_data.append(s)
    return show_data


def IsNumber(text):
    """
    Check if it is a number
    :param text: eg. 12|abc|IT
    :return:  True or False
    """
    try:
        float(text)
        return True
    except ValueError:
        return False


def CheckMd5(data):
    """
    Calculate the MD5 value of the employee information table, which is used to judge whether there is any modification in the program
    :param data:
    :return:
    """
    data_md5 = hashlib.md5(str(data).encode('utf-8'))
    hex_md5 = data_md5.hexdigest()
    return hex_md5


def ConditionMatch (con_col, con_val, con_decide):
    """
    condition match
    :param con_col:  eg. age,name,phone,...
    :param con_val: eg. 22,Jushua Cheng,13636363636,...
    :param con_decide:  eg. =,>,<,like,>=,<=
    :return: matched data eg. {'age':[22,33],'name':['Jushua Cheng','Wayne Zheng'],'phone':[13636363636,18686868686]}
    """
    con_matched = MakeTempDict()
    for idx, n in enumerate(STAFF_DATA[con_col]):
        # The value with size judgment must be a number type
        if con_decide in ['<', '>', '>=', '<=']:
            con_val, n = float(con_val), float(n) if IsNumber(con_val) and IsNumber(n) \
                else Log('Syntax error: <or> conditional judgment only supports numeric keywords and fields', 'error')
        if con_decide == '=':
            if con_val == n:
                for column in DB_COLUMN:
                    con_matched[column].append(STAFF_DATA[column][idx])
        elif con_decide == '>':
            if con_val < n:
                for column in DB_COLUMN:
                    con_matched[column].append(STAFF_DATA[column][idx])
        elif con_decide == '<':
            if con_val > n:
                for column in DB_COLUMN:
                    con_matched[column].append(STAFF_DATA[column][idx])
        elif con_decide == '>=':
            if con_val <= n:
                for column in DB_COLUMN:
                    con_matched[column].append(STAFF_DATA[column][idx])
        elif con_decide == '<=':
            if con_val >= n:
                for column in DB_COLUMN:
                    con_matched[column].append(STAFF_DATA[column][idx])
        elif con_decide == 'like':
            if con_val in n:
                for column in DB_COLUMN:
                    con_matched[column].append(STAFF_DATA[column][idx])

    if con_matched != MakeTempDict(): # If the conditional query is still the same as the initial list, it means that no data is matched, and the return is empty
        print('con_matched:', con_matched)
        return con_matched
    else:
        return None


def AddAction(add_statement, add_staff_data):
    """
    find action matches
    :param add_statement:  eg. add staff_table Wayne Zheng,28,18666888866,IT,2017-09-25
    :param add_staff_data:  STAFF_DATA
    :return:
    """
    global STAFF_DATA
    # Intercept the new data field and convert it to a list
    add_staff_info = add_statement.split('staff_table')[1]
    add_staff_info_list = [i.strip() for i in add_staff_info.split(',')]

    # id increments automatically, calculates the next id number, and inserts the id into add_staff_info_list
    # max_id = len(add_staff_data['id'])
    # next_id = max_id + 1
    # The above methods are not rigorous (when a certain piece of data is deleted, it will be a problem to use the len length to get the maximum number of ids)
    next_id = max(add_staff_data['id']) + 1
    add_staff_info_list.insert(0, str(next_id))

    if not IsNumber(add_staff_info_list[2]):
        Log('Syntax error: [age] field must be a number', 'error')

    if not IsNumber(add_staff_info_list[3]):
        Log('Syntax error: [phone] field must be a number', 'error')

    if add_staff_info_list[3] in add_staff_data['phone']:
        Log('Primary key conflict: The [phone] field is the primary key, and the [{}] number already exists in the staff_table table'.format(add_staff_info_list[3]), 'error')
    else:
        for idx, n in enumerate(DB_COLUMN):
            add_staff_data[n].append(add_staff_info_list[idx])

        STAFF_DATA = add_staff_data # All newly added or modified data are temporarily stored in STAFF_DATA, and then stored in the file when exiting the program
        # SaveStaffDB(STAFF_DB, add_staff_info_list, 'a')
        print(tabulate([add_staff_info_list], DB_COLUMN, 'fancy_grid'))
        Log('Add [1] data', 'info')


def FindAction(find_statement, temp_matched_data):
    """
    add action match
    :param temp_matched_data:
    eg.  {'dept': ['IT'], 'age': ['21'], 'id': ['3'], 'name': ['Rain Wang'],
          'in_data': ['2017‐04‐01'], 'phone': ['13451054608']}
    :param find_statement: eg. find * from staff_table | find name,age fro staff_table
    :return:
    """
    filter_key = find_statement.split('find')[1].split('from')[0]
    find_columns = [i.strip() for i in filter_key.split(',')]
    final_data = []

    # * represents all fields
    if '*' in find_columns:
        if len(find_columns) == 1:
            find_columns = DB_COLUMN
        else:
            Log('Action syntax error: * cannot exist with other columns!', 'error')
            return False

    # The filter field is empty and an error is reported
    if len(find_columns) == 1:
        if not find_columns[0]:
            Log('Action syntax error: There must be a field or *', 'error') between find and from
            return False

    # Filter the data of the columns age, name, .. after the find and temporarily store it in the new list
    for i in find_columns:
        row = temp_matched_data[i]
        final_data.append(row)

    new_final_data = DisplayDataTransform(final_data)
    print(tabulate(new_final_data, find_columns, 'fancy_grid'))
    Log('Query [{}] pieces of data'.format(len(new_final_data)), 'info')


def DelAction(del_statement, del_staff_data):
    """
    del action match
    : param del_staff_data:
    :return:
    """
    global STAFF_DATA

    # Get the id of the data to be deleted
    del_id_list = [i for i in del_staff_data['id']]

    # Delete the data of the id matched by the condition from the staff_table
    for i in del_id_list:
        del_idx = STAFF_DATA['id'].index(i)
        for k in STAFF_DATA:
            del STAFF_DATA[k][del_idx]

    # Convert the data matching the condition into a list for printing {'key':[123,'abc'],'key2':[234,'ccd']} to [[123,'abc'],[234 ,'ccd']]
    del_staff_list = []
    for k in DB_COLUMN: # Here you must use the DB_COLUMN list to get the key, otherwise the later fields and values ​​cannot correspond
        del_staff_list.append(del_staff_data[k])

    del_list = DisplayDataTransform(del_staff_list)
    print(tabulate(del_list, DB_COLUMN, 'fancy_grid'))
    Log('Deleted [{}] data'.format(len(del_list)), 'error')


def UpdateAction(update_statement, update_staff_data):
    """
    update action matches
    :param update_statement:
    :param update_staff_data:
    :return:
    """
    global STAFF_DATA
    print(update_statement)
    print(update_staff_data)

    if 'set' not in update_statement:
        Log('Action syntax error: The [update] statement is missing the necessary [set] keyword', 'error')
        return None

    set_statement = update_statement.split('set')[1]

    if '=' not in set_statement:
        Log('Action syntax error: The [set] keyword value in the [update] statement must be [=]]', 'error')
        return None

    # Take the field to be set (modified) and the value to be set (modified)
    set_col, set_val = set_statement.split('=')
    set_col, set_val = set_col.strip(), set_val.replace('"', '').replace("'", "").strip()
    print(set_col, set_val)

    # Take out the id in the data matched by the condition, then find the index of the id in STAFF_DATA, and change the field to be set to the value to be set
    # By the way, the data matched by the condition is also modified
    for i in update_staff_data['id']:
        idx, idx_1 = STAFF_DATA['id'].index(i), update_staff_data['id'].index(i)
        STAFF_DATA[set_col][idx] = set_val
        update_staff_data[set_col][idx_1] = set_val

    # The modified matching data is converted into a list for printing
    print(update_staff_data)
    update_staff_list = [update_staff_data[k] for k in DB_COLUMN] # Remember to always use DB_COLUMN for field order
    print(update_staff_list)
    show_update_list = DisplayDataTransform(update_staff_list)
    print(tabulate(show_update_list, DB_COLUMN, 'fancy_grid'))
    Log('modified [{}] pieces of data'.format(len(update_staff_data['id'])), 'info')


def WhereStatement(where_statement):
    """
    Parse the where conditional statement, and distribute the clause to the function query matching records of each conditional query
    :param where_statement:
    :return:
    """
    decide_list = ['>=', '<=', '>', '<', '=', 'like']

    for k in decide_list:
        if k in where_statement:

            col, val = where_statement.split(k)
            col, val = col.replace("'", "").replace('"', '').strip(), val.replace("'", "").replace('"', '').strip()

            if col and val: # There must be a value on both sides of the conditional judgment symbol and the field exists before proceeding to the next step eg. age > 20
                if col not in DB_COLUMN:
                    Log('Condition syntax error: There is no [{}] field in the staff_table table'.format(col), 'error')
                    return False
                con_match_record = ConditionMatch(col, val, k)
                return con_match_record
            else:
                Log('Condition syntax error: Conditional statement is missing after the [where] keyword', 'error')
    else:
        Log('Condition syntax error: The correct judgment character is missing in the conditional statement [=,>,<,<=,>=,like]', 'error')


def StatementAnalysis(cmd):
    """
    Parse the entire sentence input by the user, and hand over the relevant grammar to the relevant grammar function for further analysis operations
    :param cmd: user_cmd
    :return:
    """
    action_list = {
        'find': FindAction,
        'add': AddAction,
        'del': DelAction,
        'update': UpdateAction
    }

    # If there is a where keyword, parse the matching record according to the statement following where
    if cmd.split()[0] in action_list and 'staff_table' in cmd:
        action_name = cmd.split()[0]

        if 'where' in cmd:
            action_statement, condition_statement = cmd.strip().split('where')
            if 'from' not in action_statement and action_name not in ['add', 'update']: # Check if there is from keyword in action statements other than add update, if not, report an error and exit the program
                Log('Syntax error: The query statement is missing the <from> keyword', 'error')
                return False

            match_record = WhereStatement(condition_statement.strip())

            # If the condition matches with data, continue to the next action and field filter
            if match_record:
                action_list[action_name](action_statement, match_record)

        # If there is no where condition, return all records to the corresponding action to continue processing
        else:
            action_list[action_name](cmd, STAFF_DATA)
    else:
        Log('Syntax error: Please check help information', 'error')


def main():
    """
    Program main entry
    :return:
    """
    while True:
        cmd = input("[StaffDB]>>: ").strip()
        if not cmd:
            continue
        elif cmd.strip().split()[0] == 'help':
            Help(cmd)
        elif cmd.strip() in ['exit', 'q']:
            choice = input('\033[1;35m Please press [s] to save the changes; press any key to exit without saving >>: \033[0m').strip()
            if choice not in ['s', 'S']:
                break
            SaveStaffDB(STAFF_DB, STAFF_DATA)
            break
        else:
            StatementAnalysis(cmd)


if __name__ == '__main__':
    STAFF_DATA = LoadStaffDB(STAFF_DB)
    STAFF_DATA_MD5_A = CheckMd5(STAFF_DATA)
    main()

  

 

user_db : 

1,Alex Li,22,13651054608,Develop,2013-04-01 
2,Jack Wang,28,13451024608,HR,2015-01-07
3,Rain Wang,21,13451054608,Develop,2017-04-01
4, Mack Qiao,44,15653354208,Sales,2016-02-01
5,Rachel Chen,23,13351024606,Develop,2013-03-16
6,Eric Liu,19,18531054602,Marketing,2012-12-01
7,Chao Zhang ,21,13235324334,Administration,2011-08-08
8,Kevin Chen,22,13151054603,Sales,2013-04-01
9,Shit Wen,20,13351024602,Develop,2017-07-03
10,Shanshan Du,26 , 13698424612, public, 2017-07-02
12, Zayne Wu, 29, 18666668811, HR, 2017-06-23

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325166281&siteId=291194637