一个简单的脚本,没有复杂的操作,主要目的是对主线代码和上线的产品代码进行对比,验证两份代码的一致性
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: dgz
@contact: duanguozeng@linezonedata.com
@software: pycharm
@file: md5_encryption.py
@time: 2020/08/11
@desc: 该脚本主要目的是: 生成版本代码的MD5码, 用于对比项目代码和产品代码是否相同
"""
import os
import sys
import chardet
import hashlib
BASE_PATH = os.path.abspath('.')
class Md5Encryption(object):
def __init__(self, base_path):
# 项目主路径
self.base_path = base_path
# 收集文件列表
self.files_list = []
# 收集MD5码文件
self.md5_file = self.base_path + '/md5.txt'
# 换行分隔符的模式
self.tp = ''
# 自定义数据
self.custom = None
# 默认忽略的文件和文件夹 这里的文件或文件夹,需要填入相对路径
self.ignore = ['.git', '.idea', 'md5_encryption.py', '/src/extend_dev']
def custom_files_or_dirs(self):
"""
自定义生成MD5码的文件
-D: 不生成MD5码的文件夹
-d: 仅生成指定文件夹下文件的MD5码
-F: 不生MD5码的文件
-f: 仅对指定文件生成MD5码
-a: 对所有文件进行处理
-h: 帮助
:return: custom_list
"""
print('开始按照{0}模式收集文件'.format(sys.argv[1]))
if '-D' == sys.argv[1]:
custom_list = sys.argv[2:]
files_list = self.ignore_dirs(self.base_path, custom_list)
elif '-d' == sys.argv[1]:
custom_list = sys.argv[2:]
files_list = self.dispose_dirs(self.base_path, custom_list)
elif '-F' == sys.argv[1]:
custom_list = sys.argv[2:]
files_list = self.dispose_files(self.base_path, custom_list)
print(len(files_list))
elif '-f' == sys.argv[1]:
custom_list = sys.argv[2:]
files_list = self.dispose_files(self.base_path, custom_list)
print(len(files_list))
elif '-a' == sys.argv[1]:
files_list = self.get_all_files(self.base_path)
elif '-h' == sys.argv[1]:
print("""
1. 脚本执行命令: python md5_encryption.py [自定义数据参数类型] [数据]
2. 自定义数据参数类型有: '-D', '-d', '-F'. '-f'
3. 数据之间用空格隔开
4. -D: 不生成MD5码的文件夹
-d: 仅生成指定文件夹下文件的MD5码
-F: 不生MD5码的文件
-f: 仅对指定文件生成MD5码
-a: 对所有文件进行处理
-h: 帮助
""")
sys.exit(0)
else:
raise Exception('自定义数据参数传入异常, 仅支持 "-D", "-d", "-F", "-f" 参数选择')
if not files_list:
raise TypeError('文件收集异常,当前文件收集模式是{0}'.format(sys.argv[1]))
else:
lz_logger.info('文件收集成功')
return files_list
def ignore_dirs(self, base_path, custom_list=None):
"""
忽略不生成MD5的文件夹
:param base_path: 路径
:param custom_list: 待处理的文件列表
:return: 收集的文件列表
"""
base_dir_list = self.ignore_default_dir(base_path)
for item in base_dir_list:
src = os.path.join(base_path, item)
if os.path.exists(src):
if item in custom_list:
continue
else:
if os.path.isdir(src):
self.ignore_dirs(src, custom_list)
else:
self.add_files(src)
else:
raise IOError('{}: 当前文件或文件夹不存在!'.format(src))
return self.files_list
def dispose_dirs(self, base_path, custom_list=None):
"""
处理需要生成MD5的文件夹
:param base_path: 路径
:param custom_list: 待处理的文件列表
:return: self.files_list
"""
if base_path != self.base_path:
custom_list = os.listdir(base_path)
custom_list.sort()
for item in custom_list:
src = os.path.join(base_path, item)
if os.path.exists(src):
if os.path.isdir(src):
self.dispose_dirs(src, custom_list)
else:
self.add_files(src)
else:
raise IOError('{}: 当前文件或文件夹不存在!'.format(src))
return self.files_list
def dispose_files(self, base_path, custom_list=None):
"""
处理文件
:param base_path: 文件路径
:param custom_list: 待处理的文件列表
:return: self.files_list
"""
base_dir_list = self.ignore_default_dir(base_path)
for item in base_dir_list:
src = os.path.join(base_path, item)
if os.path.exists(src):
if os.path.isdir(src):
self.dispose_files(src, custom_list)
else:
if sys.argv[1] == '-F':
if item in custom_list:
continue
else:
self.add_files(src)
elif sys.argv[1] == "-f":
if item in custom_list:
self.add_files(src)
else:
continue
else:
raise IOError('{}: 当前文件或文件夹不存在!'.format(src))
return self.files_list
def ignore_default_dir(self, base_path):
"""
处理默认需要忽略掉的文件或文件夹
:param base_path: 路径
:return: base_dir_list
"""
base_dir_list = os.listdir(base_path)
base_dir_abs_path_list = [os.path.join(base_path, path) for path in base_dir_list]
for data in self.ignore:
data_path = self.base_path + data
if data_path in base_dir_abs_path_list:
base_dir_list.remove(os.path.basename(data_path))
base_dir_list.sort()
return base_dir_list
def get_all_files(self, base_path):
"""
获取所有.py或.sh结尾的文件列表
:param base_path: 文件路径
:return: self.files_list
"""
base_dir_list = self.ignore_default_dir(base_path)
for item in base_dir_list:
src = os.path.join(base_path, item)
if os.path.exists(src):
if os.path.isdir(src):
self.get_all_files(src)
else:
self.add_files(src)
else:
raise IOError('{}: 当前文件或文件夹不存在!'.format(src))
return self.files_list
def add_files(self, path):
if os.path.exists(path):
if os.path.isfile(path):
if (os.path.basename(path).endswith('.py') or os.path.basename(path).endswith('.sh')) \
and os.path.getsize(path) != 0:
self.files_list.append(path)
else:
raise IOError('{}: 当前文件不存在!'.format(path))
return True
def format_conversion(self, files_list):
"""
用于同一文件的格式, 不同操作系统的换行分割符不同,会导致同一代码的MD5码不同
所以统一项目的换行分割符是 LF
:param files_list:
:return:
"""
if not files_list:
raise TypeError('文件列表为空, 收集文件时异常')
else:
for path in files_list:
file_name = os.path.basename(path)
print('开始格式化: {0}'.format(file_name))
read_data = self.read_file(path)
encoding = chardet.detect(read_data)['encoding']
data_str = read_data.decode(encoding)
print('{0}: 文件正常读入'.format(file_name))
if '\r\n' in data_str:
self.tp = "CRLF"
data_str = data_str.replace('\r\n', '\n')
print('将Windows系统的换行分割符CRLF转为Unix and OS X 的 LF')
elif '\r' in data_str:
self.tp = "CR"
data_str = data_str.replace('\r', '\n')
print('将Classic Mac系统的换行分割符CRLF转为Unix and OS X 的 LF')
if encoding not in ['utf-8', 'ascii'] or self.tp == 'CR' or self.tp == 'CRLF':
try:
with open(path, 'w', newline='\n', encoding='utf-8') as f:
f.write(data_str)
except IOError:
print('格式化文件{0}写入异常'.format(file_name))
print('格式化文件{0}写入异常'.format(file_name))
print('格式化: {0} 成功'.format(file_name))
print('文件格式统一化完成')
return True
def generate_hash(self, files_list):
"""
用于生成每一个文件对应的MD5码
:param files_list:
:return:
"""
if files_list:
for file_path in files_list:
file = os.path.basename(file_path)
data = self.read_file(file_path)
md5 = hashlib.md5(data).hexdigest() # 生成MD5
lz_logger.info('{0} 的MD5码: {1}'.format(file, md5))
md5_data = file + ": " + md5
md5_data = md5_data.encode("utf-8")
self.write_md5_code_to_file(self.md5_file, md5_data)
lz_logger.info('成功生成所有文件的MD5码')
else:
raise TypeError('files_list is empty! 文件收集异常')
return True
def version_md5(self):
"""
生成版本对应的MD5码
:return:
"""
data = self.read_file(self.md5_file)
md5 = hashlib.md5(data).hexdigest()
md5_code = 'Version_md5_code:' + md5
print('Version_md5_code: {0} '.format(md5))
md5_code = md5_code.encode("utf-8")
self.write_md5_code_to_file(self.md5_file, md5_code)
print('当前版本MD5码成功生成')
return True
def read_file(self, file_path):
"""
读取文件内容
:param file_path:
:return:
"""
if not os.path.exists(file_path):
raise IOError("读取文件失败! 文件不存在")
else:
try:
with open(file_path, 'rb') as f:
data = f.read()
return data
except IOError:
print('文件读取异常')
def write_md5_code_to_file(self, file_path=None, data=None):
"""
写入文件内容
:param file_path:
:param data:
:return:
"""
try:
with open(file_path, 'ab+') as f:
write_data = data + '\n'.encode()
f.write(write_data)
except IOError:
print('文件写入异常')
def check_file(self):
if os.path.exists(self.md5_file):
print('收集MD5码文件已存在')
os.remove(self.md5_file)
print('删除MD5码收集文件')
else:
print('收集MD5码文件不存在')
return True
def run(self):
files_list = self.custom_files_or_dirs()
print('检查收集MD5码文件是否存在')
self.check_file()
print('开始统一文件格式')
self.format_conversion(files_list)
print('开始对每个文件生成对应MD5码')
self.generate_hash(files_list)
print('生成版本MD5码')
self.version_md5()
if __name__ == '__main__':
"""
脚本基本说明:
目的: 为了验证项目主题代码和产品主体代码是否一致, 通过对每个.py文件和.sh文件生成对应的MD5码,来生成对应版本的MD5码
使用说明:
基础命令: python md5_encryption.py 数据收集模式 数据1 数据2 数据3 ....
注意: 数据收集模式 和 数据 之间使用空格隔开
数据收集模式有:
-D: 过滤不进行MD5码生成的文件夹
-d: 仅生成指定文件夹下文件的MD5码
-F: 过滤不进行MD5码生成的文件
-f: 仅对指定文件生成MD5码
-a: 对所有文件进行处理
-h: 帮助
数据: 只需要输入文件名或文件夹名, 中间空格隔开
"""
md5_encry = Md5Encryption(BASE_PATH)
md5_encry.run()