1#!/usr/bin/python 2""" 3Utility for building the CDD from component markdown files. 4 5From the compatibility/cdd directory, run: 6python make-cdd.py --version <version number> --branch <AOSP branch> 7 --output <output file name> 8 9Each generated CDD file is marked with a hash based on the content of the input files. 10 11TODO(gdimino): Clean up and comment this code. 12""" 13 14from bs4 import BeautifulSoup 15import argparse 16import codecs 17import jinja2 18import markdown 19import os 20import re 21import subprocess 22import tidylib 23 24 25HEADERS_FOR_TOC = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7'] 26global ANDROID_VERSION 27ANDROID_VERSION = "10" 28TOC_PER_COL = 34 29 30def get_section_info(my_path): 31 # (_, _, filenames) = os.walk(my_path).next() 32 section_info = []; 33 # Get section info from every file whose name contains a number. TODO: fix 34 # this ugly hack. 35 # for rootdir, subdirs, files in os.walk(my_path): 36 for dir in get_immediate_subdirs(my_path): 37 # for dir in subdirs: 38 if (not dir.isalpha() and dir != 'older-versions' and dir != '.git'): 39 child_data = [] 40 print 'dir = ' + dir 41 for file in os.listdir(dir): 42 if '.md' in file: 43 if file == 'index.md': 44 number = 0 45 else: 46 number = int((file.split('_')[1])) 47 print 'file = ' + file + ', dir = ' + dir 48 html_string = markdown.markdown(codecs.open(my_path + '/' + dir + '/' + file, 'r', encoding='utf-8').read()) 49 child_data.append({'file': file, 50 'number': number, 51 'title': dir.split('_')[-1], 52 'html': html_string, 53 'children':[]}) 54 child_data.sort(key=lambda child: child['number']) 55 section_info.append({'id': dir, 56 'number': int(''.join((dir.split('_')[:-1])).replace("_", ".")), 57 'title': dir.split('_')[-1], 58 'html': '', 59 'children':child_data}) 60 section_info.sort(key=lambda section: section['number']) 61 return section_info 62 63 64def get_soup(section_info, version): 65 html_body_text = u'''<!DOCTYPE html> 66<head> 67<title>Android ''' + version + ''' Compatibility Definition</title> 68<link rel="stylesheet" type="text/css" href="source/android-cdd.css"/> 69<meta charset="utf-8" /> 70</head> 71<body> 72<div id="main">''' 73 74 for section in section_info: 75 for child in section['children']: 76 html_body_text += child['html'] 77 html_body_text += '</div></body><html>' 78 return BeautifulSoup(html_body_text) 79 80def get_soup_devsite(section_info): 81 html_body_text = '' 82 for section in section_info: 83 for child in section['children']: 84 html_body_text += child['html'] 85 return BeautifulSoup(html_body_text) 86 87 88def add_id_to_section_headers(soup): 89 header_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7'] 90 for tag in soup.find_all(header_tags): 91 tag['id'] = create_id(tag) 92 93def generate_toc(soup): 94 toc_html = '<div id="toc">' 95 header_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7'] 96 toc_entries = soup.find_all(header_tags) 97 toc_chunks = [toc_entries[i:i + TOC_PER_COL] for i in xrange(0, len(toc_entries), TOC_PER_COL)] 98 print 'Number of chunks = %d' % len(toc_chunks) 99 for chunk in toc_chunks: 100 if not toc_chunks.index(chunk) %2: 101 toc_html = toc_html + ('<div id="toc_left">') 102 for tag in chunk: 103 toc_html = toc_html + '<p class="toc_' + tag.name + '"><a href= "#' + create_id(tag) + '">' + tag.contents[0] + '</a></p>' 104 toc_html = toc_html + ('</div>') 105 else: 106 toc_html = toc_html + ('<div id="toc_right">') 107 for tag in chunk: 108 toc_html = toc_html + '<p class="toc_' + tag.name + '"><a href= "#' + create_id(tag) + '">' + tag.contents[0] + '</a></p>' 109 toc_html = toc_html + ('</div>') 110 toc_html = toc_html + '<div style="clear: both; page-break-after:always; height:1px"></div>' 111 toc_html = toc_html + '<div style="clear: both"></div>' 112 return (BeautifulSoup(toc_html).body.contents) 113 114def add_toc(soup): 115 toc_contents = generate_toc(soup)[0] 116 toc_title = BeautifulSoup("<h6>Table of Contents</h6>").body.contents[0] 117 soup.body.insert(0, toc_contents) 118 soup.body.insert(0, toc_title) 119 return soup 120 121def create_id(header_tag): 122 return header_tag.contents[0].lower().replace('. ', '_').replace(' ', '_').replace('.', '_') 123 124def decrease_headings(soup): 125 heading_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8'] 126 headings = soup.find_all(heading_tags) 127 for heading in headings: 128 level = int(re.search(r'(\d)', heading.name).groups()[0]) 129 heading.name = 'h%d' % (level + 1) 130 return soup 131 132def get_version_branch_and_output(): 133 # Get command-line args. If there aren't any, then prompt for user input. 134 parser = argparse.ArgumentParser() 135 parser.add_argument('--version', help='Android version') 136 parser.add_argument('--branch', help='AOSP branch') 137 parser.add_argument('--output', help='Base name of output file') 138 args = parser.parse_args() 139 140 if not args.version: 141 args.version = raw_input('Android version for CDD: ') 142 if not args.branch: 143 args.branch = raw_input('Current AOSP branch for changelog: ') 144 if not args.output: 145 args.output = raw_input('Base name of desired output file: ') 146 return (args.version, args.branch, args.output) 147 148# Utilities 149def get_immediate_subdirs(dir): 150 return [name for name in os.listdir(dir) 151 if os.path.isdir(os.path.join(dir, name))] 152 153def render_content(page_info, template_filename): 154 fp = open(template_filename) 155 temp_file = fp.read().encode('utf8') 156 fp.close() 157 return jinja2.Template(temp_file).render(page_info) 158 159# Odds and ends 160 161def check_section_numbering(soup): 162 header_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7'] 163 for tag in header_tags: 164 headings = soup.find_all(tag) 165 header_numbers = [] 166 for heading in headings: 167 header_numbers.append(re.sub(r"([\d.]*).*", r"\1"), heading.contents) 168 return true 169 170# Abandoned in favor of tidy. 171def elim_para_whitespace(html): 172 new_html = re.sub(re.compile(r"(<p[^>]*>)\s*\n\s*(<a[^>]*>)\n([^<]*)\n\s*(</a>)\n\s*(</p>)", re.M),r"\1\2\3\4\5\n", html) 173 return new_html 174 175 176def elim_space_before_punc(html): 177 new_html = re.sub(re.compile(r"</a>\s+([.,;:])", re.M),r"</a>\1", html) 178 return new_html 179 180 181def main(): 182 # Read version and branch info and output file name. 183 global ANDROID_VERSION 184 (ANDROID_VERSION, CURRENT_BRANCH, output_filename) = get_version_branch_and_output() 185 186 # Scan current directory for source files and compile info for the toc.. 187 my_path = os.getcwd() 188 section_info = get_section_info(my_path) 189 190 # Get page info 191 page_info = { 'title': 'Android ANDROID_VERSION Compatibility Definition', 192 'book_path': '/_book.yaml', 193 'project_path': '/_project.yaml' 194 } 195 196 # Generate the HTML for PDF 197 soup = get_soup(section_info, ANDROID_VERSION) 198 add_id_to_section_headers(soup) 199 add_toc(soup) 200 html = soup.prettify(formatter='html') 201 202 # Generate the HTML for devsite 203 devsite_soup = get_soup_devsite(section_info) 204 add_id_to_section_headers(devsite_soup) 205 add_id_to_section_headers(soup) 206 page_info['body_html'] = decrease_headings(devsite_soup) 207 devsite_html = render_content(page_info, 'source/devsite_template.html') 208 209 html = soup.prettify(formatter='html') 210 211 # Add version and branch info 212 html = re.sub(re.compile(r'ANDROID_VERSION'), ANDROID_VERSION, html) 213 html = re.sub(re.compile(r'CURRENT_BRANCH'), CURRENT_BRANCH, html) 214 215 devsite_html = re.sub(re.compile(r'ANDROID_VERSION'), ANDROID_VERSION, devsite_html) 216 devsite_html = re.sub(re.compile(r'CURRENT_BRANCH'), CURRENT_BRANCH, devsite_html) 217 218 # Apply HTML Tidy to output 219 (document, errors) = tidylib.tidy_document(html, options={'doctype': 'omit'}) 220 (devsite_document, errors) = tidylib.tidy_document(devsite_html, options={'doctype': 'omit'}) 221 222 # Eliminate space before punctuation 223 html = elim_space_before_punc(html) 224 devsite_html = elim_space_before_punc(devsite_html) 225 226 # Write output files 227 output = codecs.open('%s.html' % output_filename, 'w', encoding='utf-8') 228 output.write(document) 229 output.close() 230 231 devsite_output = codecs.open('%s-devsite.html' % output_filename, 'w', encoding='utf-8') 232 devsite_output.write(devsite_document) 233 output.close() 234 235 # Code to generate PDF 236 # TODO(gdimino) 237 238 # subprocess.call('wkhtmltopdf -B 1in -T 1in -L .75in -R .75in page ' + 239 # output_filename + 240 # ' --footer-html source/android-cdd-footer.html /tmp/android-cdd-body.pdf', shell=True) 241 242if __name__ == '__main__': 243 main() 244 245 246 247 248