#!/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