1f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin#!/usr/bin/env python 2f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# 3f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# Copyright (C) 2012 The Android Open Source Project 4f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# 5f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# Licensed under the Apache License, Version 2.0 (the "License"); 6f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# you may not use this file except in compliance with the License. 7f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# You may obtain a copy of the License at 8f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# 9f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# http://www.apache.org/licenses/LICENSE-2.0 10f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# 11f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# Unless required by applicable law or agreed to in writing, software 12f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# distributed under the License is distributed on an "AS IS" BASIS, 13f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# See the License for the specific language governing permissions and 15f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin# limitations under the License. 16f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin""" 17f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel BerlinUsage: generate-notice-files [plain text output file] [html output file] [file title] [directory of notices] 18f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 19f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel BerlinGenerate the Android notice files, including both text and html files. 20f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 21f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin-h to display this usage message and exit. 22f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin""" 23f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinfrom collections import defaultdict 24f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinimport getopt 25f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinimport hashlib 26f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinimport itertools 27f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinimport os 28f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinimport os.path 29f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinimport re 30f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinimport sys 31f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 32f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel BerlinMD5_BLOCKSIZE = 1024 * 1024 33f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel BerlinHTML_ESCAPE_TABLE = { 34f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin "&": "&", 35f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin '"': """, 36f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin "'": "'", 37f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin ">": ">", 38f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin "<": "<", 39f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin } 40f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 41f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlintry: 42f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin opts, args = getopt.getopt(sys.argv[1:], "h") 43f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinexcept getopt.GetoptError, err: 44f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print str(err) 45f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print __doc__ 46f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin sys.exit(2) 47f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 48f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinfor o, a in opts: 49f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin if o == "-h": 50f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print __doc__ 51f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin sys.exit(2) 52f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin else: 53f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> sys.stderr, "unhandled option %s" % (o,) 54f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 55f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinif len(args) != 4: 56f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print """need exactly four arguments, the two output files, the file title 57f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin and the directory containing notices, not %d""" % (len(args),) 58f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print __doc__ 59f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin sys.exit(1) 60f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 61f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlindef hexify(s): 62f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin return ("%02x"*len(s)) % tuple(map(ord, s)) 63f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 64f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlindef md5sum(filename): 65f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin """Calculate an MD5 of the file given by FILENAME, 66f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin and return hex digest as a string. 67f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin Output should be compatible with md5sum command""" 68f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 69f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin f = open(filename, "rb") 70f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin sum = hashlib.md5() 71f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin while 1: 72f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin block = f.read(MD5_BLOCKSIZE) 73f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin if not block: 74f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin break 75f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin sum.update(block) 76f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin f.close() 77f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin return hexify(sum.digest()) 78f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 79f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 80f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlindef html_escape(text): 81f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin """Produce entities within text.""" 82f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin return "".join(HTML_ESCAPE_TABLE.get(c,c) for c in text) 83f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 84f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel BerlinHTML_OUTPUT_CSS=""" 85f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin<style type="text/css"> 86f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinbody { padding: 0; font-family: sans-serif; } 87f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin.same-license { background-color: #eeeeee; border-top: 20px solid white; padding: 10px; } 88f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin.label { font-weight: bold; } 89f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin.file-list { margin-left: 1em; color: blue; } 90f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin</style> 91f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin""" 92f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 93f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlindef combine_notice_files_html(file_hash, input_dir, output_filename): 94f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin """Combine notice files in FILE_HASH and output a HTML version to OUTPUT_FILENAME.""" 95f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 96f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt") 97f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 98f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin # Set up a filename to row id table (anchors inside tables don't work in 99f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin # most browsers, but href's to table row ids do) 100f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin id_table = {} 101f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin id_count = 0 102f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin for value in file_hash.values(): 103f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin for filename in value: 104f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin id_table[filename] = id_count 105f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin id_count += 1 106f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 107f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin # Open the output file, and output the header pieces 108f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin output_file = open(output_filename, "wb") 109f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 110f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "<html><head>" 111f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, HTML_OUTPUT_CSS 112f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, '</head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0">' 113f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 114f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin # Output our table of contents 115f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, '<div class="toc">' 116f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "<ul>" 117f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 118f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin # Flatten the list of lists into a single list of filenames 119f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin sorted_filenames = sorted(itertools.chain.from_iterable(file_hash.values())) 120f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 121f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin # Print out a nice table of contents 122f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin for filename in sorted_filenames: 123f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename) 124f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, '<li><a href="#id%d">%s</a></li>' % (id_table.get(filename), stripped_filename) 125f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 126f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "</ul>" 127f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "</div><!-- table of contents -->" 128f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin # Output the individual notice file lists 129f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >>output_file, '<table cellpadding="0" cellspacing="0" border="0">' 130f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin for value in file_hash.values(): 131f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, '<tr id="id%d"><td class="same-license">' % id_table.get(value[0]) 132f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, '<div class="label">Notices for file(s):</div>' 133f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, '<div class="file-list">' 134f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin for filename in sorted(value): 135f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "%s <br/>" % (SRC_DIR_STRIP_RE.sub(r"\1", filename)) 136f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "</div><!-- file-list -->" 137f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file 138f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, '<pre class="license-text">' 139f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, html_escape(open(value[0]).read()) 140f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "</pre><!-- license-text -->" 141f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "</td></tr><!-- same-license -->" 142f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file 143f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file 144f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file 145f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 146f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin # Finish off the file output 147f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "</table>" 148f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "</body></html>" 149f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin output_file.close() 150f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 151f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlindef combine_notice_files_text(file_hash, input_dir, output_filename, file_title): 152f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin """Combine notice files in FILE_HASH and output a text version to OUTPUT_FILENAME.""" 153f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 154f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt") 155f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin output_file = open(output_filename, "wb") 156f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, file_title 157f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin for value in file_hash.values(): 158f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "============================================================" 159f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "Notices for file(s):" 160f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin for filename in sorted(value): 161f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, SRC_DIR_STRIP_RE.sub(r"\1", filename) 162f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, "------------------------------------------------------------" 163f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print >> output_file, open(value[0]).read() 164f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin output_file.close() 165f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 166f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlindef main(args): 167f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin txt_output_file = args[0] 168f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin html_output_file = args[1] 169f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin file_title = args[2] 170f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 171f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin # Find all the notice files and md5 them 172f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin input_dir = os.path.normpath(args[3]) 173f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin files_with_same_hash = defaultdict(list) 174f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin for root, dir, files in os.walk(input_dir): 175f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin for file in files: 176f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin if file.endswith(".txt"): 177f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin filename = os.path.join(root, file) 178f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin file_md5sum = md5sum(filename) 179f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin files_with_same_hash[file_md5sum].append(filename) 180f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 181f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 182f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print "Combining NOTICE files into HTML" 183f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin combine_notice_files_html(files_with_same_hash, input_dir, html_output_file) 184f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin print "Combining NOTICE files into text" 185f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin combine_notice_files_text(files_with_same_hash, input_dir, txt_output_file, file_title) 186f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin 187f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlinif __name__ == "__main__": 188f5a97d7fbbda5ec8ad4ea73a8035d7fa88bbe1daDaniel Berlin main(args) 189