def main(): # logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.INFO) problems = {} # get root directory cwd = Path(__file__).parents[0] root_dir = cwd / '..' # set argprase parser = argparse.ArgumentParser( description='Generate Triple-Z/LeetCode README file automatically.') parser.add_argument('-o', '--output-file', dest='output_file_name', type=str, default='README-generated.md') args = parser.parse_args() output_file_name = args.output_file_name logging.debug('Output file name is: {}'.format(output_file_name)) # get all the files java_filelist = Path(root_dir).glob('java/src/*.java') go_filelist = Path(root_dir).glob('go/src/*.go') py3_filelist = Path(root_dir).glob('py3/*.py') cpp_filelist = Path(root_dir).glob('cpp/src/*.cpp') doc_filelist = Path(root_dir).glob('docs/*.md') java_pattern = re.compile( r'java\/src\/([\d\w\_\-\u4e00-\u9fa5|\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]+)\.?\s?[\w\-]*\.java', re.ASCII) go_pattern = re.compile( r'go\/src\/([\d\w\_\-\u4e00-\u9fa5|\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]+)\.go', re.ASCII) py3_pattern = re.compile( r'py3\/([\d\w\_\-\u4e00-\u9fa5|\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]+)\.py', re.ASCII) cpp_pattern = re.compile( r'cpp\/src\/([\d\w\_\-\u4e00-\u9fa5|\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]+)\.cpp', re.ASCII) doc_pattern = re.compile( r'docs\/([\d\w\s\_\-\u4e00-\u9fa5|\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]+)\. ([\w\d\s\'\"\(\)\-\,\.]+)?(\s?[\uFF00-\uFFFF|\u4e00-\u9fa5|\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5|\w\d\s\_\-\(\)\,\.\+]+)?\.md', re.ASCII) problem_info_pattern = re.compile( r'-\sDifficulty:\s(Easy|Medium|Hard)\s*\n-\sTopics:\s(`[`\w\d\s\,\-]+`)\s*\n-\sLink:\s((?:http|https):\/\/.*)\s*\n', re.ASCII) # add java files for java_file in java_filelist: # processing filename java_res = java_pattern.search(str(java_file)) if java_res is None: logging.warning( 'The filename {} is invalid, ignored.'.format(java_file)) continue number = java_res.group(1).replace("_", " ") logging.debug('processing {} java file'.format(number)) number_problem = problems.get(number) rel_java_file = java_file.relative_to(root_dir) if number_problem is None: new_problem = Problem() new_problem.number = number new_problem.is_solved = True new_problem.java = quote(str(rel_java_file)) problems[number] = new_problem else: number_problem.java = quote(str(rel_java_file)) number_problem.is_solved = True # add go files for go_file in go_filelist: go_res = go_pattern.search(str(go_file)) if go_res is None: logging.warning( 'The filename {} is invalid, ignored.'.format(go_file)) continue number = go_res.group(1).replace("_", " ") logging.debug('processing {} go file'.format(number)) number_problem = problems.get(number) rel_go_file = go_file.relative_to(root_dir) if number_problem is None: new_problem = Problem() new_problem.number = number new_problem.is_solved = True new_problem.go = quote(str(rel_go_file)) problems[number] = new_problem else: number_problem.go = quote(str(rel_go_file)) number_problem.is_solved = True # add python3 files for py3_file in py3_filelist: py3_res = py3_pattern.search(str(py3_file)) if py3_res is None: logging.warning( 'The filename {} is invalid, ignored.'.format(py3_file)) continue number = py3_res.group(1).replace("_", " ") logging.debug('processing {} python3 file'.format(number)) number_problem = problems.get(number) rel_py3_file = py3_file.relative_to(root_dir) if number_problem is None: new_problem = Problem() new_problem.number = number new_problem.is_solved = True new_problem.py3 = quote(str(rel_py3_file)) problems[number] = new_problem else: number_problem.py3 = quote(str(rel_py3_file)) number_problem.is_solved = True # add cpp files for cpp_file in cpp_filelist: cpp_res = cpp_pattern.search(str(cpp_file)) if cpp_res is None: logging.warning( 'The filename {} is invalid, ignored.'.format(cpp_file)) continue number = cpp_res.group(1).replace("_", " ") logging.debug('processing {} cpp file'.format(number)) number_problem = problems.get(number) rel_cpp_file = cpp_file.relative_to(root_dir) if number_problem is None: new_problem = Problem() new_problem.number = number new_problem.is_solved = True new_problem.cpp = quote(str(rel_cpp_file)) problems[number] = new_problem else: number_problem.cpp = quote(str(rel_cpp_file)) number_problem.is_solved = True # add docs for doc in doc_filelist: doc_res = doc_pattern.search(str(doc)) if doc_res is None: logging.warning('The filename {} is invalid, ignored.'.format(doc)) continue number = doc_res.group(1) title_en = doc_res.group(2) if title_en == "": title_en = None title_zh = doc_res.group(3) logging.debug('processing {} doc file, {}, {}'.format( number, title_en, title_zh)) # open the doc to get problem info difficulty, topics, problem_link = None, None, None with open(str(doc), 'r') as doc_file: # read the first 10 lines for each doc file file_head = [next(doc_file) for x in range(10)] file_head = ''.join(file_head) file_res = problem_info_pattern.search(file_head) if file_res is None: logging.warning( 'Cannot get problem info in {} , ignored'.format(doc)) logging.debug(file_head) else: difficulty = file_res.group(1) topics = file_res.group(2) problem_link = file_res.group(3) number_problem = problems.get(number) rel_doc_file = doc.relative_to(root_dir) if number_problem is None: new_problem = Problem() new_problem.number = number new_problem.title_en = title_en new_problem.title_zh = title_zh new_problem.difficulty = difficulty new_problem.topics = topics new_problem.link = problem_link new_problem.doc = quote(str(rel_doc_file)) problems[number] = new_problem else: number_problem.title_en = title_en number_problem.title_zh = title_zh number_problem.difficulty = difficulty number_problem.topics = topics number_problem.link = problem_link number_problem.doc = quote(str(rel_doc_file)) # sort problems by its number def cmp(o: str): if o.isdigit(): return int(o) elif o.startswith("剑指"): if len(o.split()) > 5: # 剑指 Offer XX - II return 10000 + int(o.split()[2]) * 10 + len(o.split()[4]) return 10000 + int(o.split()[2]) * 10 problems_number_list = sorted(problems, key=cmp) problems_list = [] for problem in problems_number_list: problems_list.append(problems[problem]) # logging.debug(problems_list) # get jinja2 template loader loader = FileSystemLoader(root_dir / 'utils' / 'templates') env = Environment(loader=loader) ### Generate topics topics_map = {} for problem in problems_list: if problem.topics is None: continue problem_raw_topics = problem.topics.split('`') problem_topics = [] for t in problem_raw_topics: if len(t) > 0 and "," not in t: # valid topic problem_topics.append(t) for topic in problem_topics: if topics_map.get(topic) is None: topics_map[topic] = [] topics_map[topic].append(problem) topics = list(topics_map.keys()) topics.sort() # render each {TOPIC}.md topic_template = env.get_template('TOPIC.md.j2') for topic in topics: topic_generated = topic_template.render( topic=topic, problems_list=topics_map[topic]) # logging.debug(topic_generated) with open(str(root_dir / 'docs' / 'topics' / '{}.md'.format(topic)), 'w') as topic_file: topic_file.write(topic_generated) logging.debug("{} topic file is generated.".format(topic)) # using jinja2 render the README file readme_template = env.get_template('README.md.j2') readme_template.globals['now'] = datetime.utcnow readme_generated = readme_template.render(problems_list=problems_list, topics=topics) logging.debug(readme_generated) # update README.md with open(str(root_dir / output_file_name), 'w') as readme: readme.write(readme_generated) logging.info(""" The new README file has written to {}, go there and check it 🎉 If you are satisfied with this generated README file, just type `make update-change`. """.format(output_file_name))