|  | #!/usr/bin/env python | 
|  | # | 
|  | #===- git-clang-format - ClangFormat Git Integration ---------*- python -*--===# | 
|  | # | 
|  | #                     The LLVM Compiler Infrastructure | 
|  | # | 
|  | # This file is distributed under the University of Illinois Open Source | 
|  | # License. See LICENSE.TXT for details. | 
|  | # | 
|  | #===------------------------------------------------------------------------===# | 
|  |  | 
|  | r""" | 
|  | clang-format git integration | 
|  | ============================ | 
|  |  | 
|  | This file provides a clang-format integration for git. Put it somewhere in your | 
|  | path and ensure that it is executable. Then, "git clang-format" will invoke | 
|  | clang-format on the changes in current files or a specific commit. | 
|  |  | 
|  | For further details, run: | 
|  | git clang-format -h | 
|  |  | 
|  | Requires Python 2.7 | 
|  | """ | 
|  |  | 
|  | from __future__ import print_function | 
|  | import argparse | 
|  | import collections | 
|  | import contextlib | 
|  | import errno | 
|  | import os | 
|  | import re | 
|  | import subprocess | 
|  | import sys | 
|  |  | 
|  | usage = 'git clang-format [OPTIONS] [<commit>] [<commit>] [--] [<file>...]' | 
|  |  | 
|  | desc = ''' | 
|  | If zero or one commits are given, run clang-format on all lines that differ | 
|  | between the working directory and <commit>, which defaults to HEAD.  Changes are | 
|  | only applied to the working directory. | 
|  |  | 
|  | If two commits are given (requires --diff), run clang-format on all lines in the | 
|  | second <commit> that differ from the first <commit>. | 
|  |  | 
|  | The following git-config settings set the default of the corresponding option: | 
|  | clangFormat.binary | 
|  | clangFormat.commit | 
|  | clangFormat.extension | 
|  | clangFormat.style | 
|  | ''' | 
|  |  | 
|  | # Name of the temporary index file in which save the output of clang-format. | 
|  | # This file is created within the .git directory. | 
|  | temp_index_basename = 'clang-format-index' | 
|  |  | 
|  |  | 
|  | Range = collections.namedtuple('Range', 'start, count') | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | config = load_git_config() | 
|  |  | 
|  | # In order to keep '--' yet allow options after positionals, we need to | 
|  | # check for '--' ourselves.  (Setting nargs='*' throws away the '--', while | 
|  | # nargs=argparse.REMAINDER disallows options after positionals.) | 
|  | argv = sys.argv[1:] | 
|  | try: | 
|  | idx = argv.index('--') | 
|  | except ValueError: | 
|  | dash_dash = [] | 
|  | else: | 
|  | dash_dash = argv[idx:] | 
|  | argv = argv[:idx] | 
|  |  | 
|  | default_extensions = ','.join([ | 
|  | # From clang/lib/Frontend/FrontendOptions.cpp, all lower case | 
|  | 'c', 'h',  # C | 
|  | 'm',  # ObjC | 
|  | 'mm',  # ObjC++ | 
|  | 'cc', 'cp', 'cpp', 'c++', 'cxx', 'hpp',  # C++ | 
|  | # Other languages that clang-format supports | 
|  | 'proto', 'protodevel',  # Protocol Buffers | 
|  | 'java',  # Java | 
|  | 'js',  # JavaScript | 
|  | 'ts',  # TypeScript | 
|  | ]) | 
|  |  | 
|  | p = argparse.ArgumentParser( | 
|  | usage=usage, formatter_class=argparse.RawDescriptionHelpFormatter, | 
|  | description=desc) | 
|  | p.add_argument('--binary', | 
|  | default=config.get('clangformat.binary', 'clang-format'), | 
|  | help='path to clang-format'), | 
|  | p.add_argument('--commit', | 
|  | default=config.get('clangformat.commit', 'HEAD'), | 
|  | help='default commit to use if none is specified'), | 
|  | p.add_argument('--diff', action='store_true', | 
|  | help='print a diff instead of applying the changes') | 
|  | p.add_argument('--extensions', | 
|  | default=config.get('clangformat.extensions', | 
|  | default_extensions), | 
|  | help=('comma-separated list of file extensions to format, ' | 
|  | 'excluding the period and case-insensitive')), | 
|  | p.add_argument('-f', '--force', action='store_true', | 
|  | help='allow changes to unstaged files') | 
|  | p.add_argument('-p', '--patch', action='store_true', | 
|  | help='select hunks interactively') | 
|  | p.add_argument('-q', '--quiet', action='count', default=0, | 
|  | help='print less information') | 
|  | p.add_argument('--style', | 
|  | default=config.get('clangformat.style', None), | 
|  | help='passed to clang-format'), | 
|  | p.add_argument('-v', '--verbose', action='count', default=0, | 
|  | help='print extra information') | 
|  | # We gather all the remaining positional arguments into 'args' since we need | 
|  | # to use some heuristics to determine whether or not <commit> was present. | 
|  | # However, to print pretty messages, we make use of metavar and help. | 
|  | p.add_argument('args', nargs='*', metavar='<commit>', | 
|  | help='revision from which to compute the diff') | 
|  | p.add_argument('ignored', nargs='*', metavar='<file>...', | 
|  | help='if specified, only consider differences in these files') | 
|  | opts = p.parse_args(argv) | 
|  |  | 
|  | opts.verbose -= opts.quiet | 
|  | del opts.quiet | 
|  |  | 
|  | commits, files = interpret_args(opts.args, dash_dash, opts.commit) | 
|  | if len(commits) > 1: | 
|  | if not opts.diff: | 
|  | die('--diff is required when two commits are given') | 
|  | else: | 
|  | if len(commits) > 2: | 
|  | die('at most two commits allowed; %d given' % len(commits)) | 
|  | changed_lines = compute_diff_and_extract_lines(commits, files) | 
|  | if opts.verbose >= 1: | 
|  | ignored_files = set(changed_lines) | 
|  | filter_by_extension(changed_lines, opts.extensions.lower().split(',')) | 
|  | if opts.verbose >= 1: | 
|  | ignored_files.difference_update(changed_lines) | 
|  | if ignored_files: | 
|  | print('Ignoring changes in the following files (wrong extension):') | 
|  | for filename in ignored_files: | 
|  | print('    %s' % filename) | 
|  | if changed_lines: | 
|  | print('Running clang-format on the following files:') | 
|  | for filename in changed_lines: | 
|  | print('    %s' % filename) | 
|  | if not changed_lines: | 
|  | print('no modified files to format') | 
|  | return | 
|  | # The computed diff outputs absolute paths, so we must cd before accessing | 
|  | # those files. | 
|  | cd_to_toplevel() | 
|  | if len(commits) > 1: | 
|  | old_tree = commits[1] | 
|  | new_tree = run_clang_format_and_save_to_tree(changed_lines, | 
|  | revision=commits[1], | 
|  | binary=opts.binary, | 
|  | style=opts.style) | 
|  | else: | 
|  | old_tree = create_tree_from_workdir(changed_lines) | 
|  | new_tree = run_clang_format_and_save_to_tree(changed_lines, | 
|  | binary=opts.binary, | 
|  | style=opts.style) | 
|  | if opts.verbose >= 1: | 
|  | print('old tree: %s' % old_tree) | 
|  | print('new tree: %s' % new_tree) | 
|  | if old_tree == new_tree: | 
|  | if opts.verbose >= 0: | 
|  | print('clang-format did not modify any files') | 
|  | elif opts.diff: | 
|  | print_diff(old_tree, new_tree) | 
|  | else: | 
|  | changed_files = apply_changes(old_tree, new_tree, force=opts.force, | 
|  | patch_mode=opts.patch) | 
|  | if (opts.verbose >= 0 and not opts.patch) or opts.verbose >= 1: | 
|  | print('changed files:') | 
|  | for filename in changed_files: | 
|  | print('    %s' % filename) | 
|  |  | 
|  |  | 
|  | def load_git_config(non_string_options=None): | 
|  | """Return the git configuration as a dictionary. | 
|  |  | 
|  | All options are assumed to be strings unless in `non_string_options`, in which | 
|  | is a dictionary mapping option name (in lower case) to either "--bool" or | 
|  | "--int".""" | 
|  | if non_string_options is None: | 
|  | non_string_options = {} | 
|  | out = {} | 
|  | for entry in run('git', 'config', '--list', '--null').split('\0'): | 
|  | if entry: | 
|  | name, value = entry.split('\n', 1) | 
|  | if name in non_string_options: | 
|  | value = run('git', 'config', non_string_options[name], name) | 
|  | out[name] = value | 
|  | return out | 
|  |  | 
|  |  | 
|  | def interpret_args(args, dash_dash, default_commit): | 
|  | """Interpret `args` as "[commits] [--] [files]" and return (commits, files). | 
|  |  | 
|  | It is assumed that "--" and everything that follows has been removed from | 
|  | args and placed in `dash_dash`. | 
|  |  | 
|  | If "--" is present (i.e., `dash_dash` is non-empty), the arguments to its | 
|  | left (if present) are taken as commits.  Otherwise, the arguments are checked | 
|  | from left to right if they are commits or files.  If commits are not given, | 
|  | a list with `default_commit` is used.""" | 
|  | if dash_dash: | 
|  | if len(args) == 0: | 
|  | commits = [default_commit] | 
|  | else: | 
|  | commits = args | 
|  | for commit in commits: | 
|  | object_type = get_object_type(commit) | 
|  | if object_type not in ('commit', 'tag'): | 
|  | if object_type is None: | 
|  | die("'%s' is not a commit" % commit) | 
|  | else: | 
|  | die("'%s' is a %s, but a commit was expected" % (commit, object_type)) | 
|  | files = dash_dash[1:] | 
|  | elif args: | 
|  | commits = [] | 
|  | while args: | 
|  | if not disambiguate_revision(args[0]): | 
|  | break | 
|  | commits.append(args.pop(0)) | 
|  | if not commits: | 
|  | commits = [default_commit] | 
|  | files = args | 
|  | else: | 
|  | commits = [default_commit] | 
|  | files = [] | 
|  | return commits, files | 
|  |  | 
|  |  | 
|  | def disambiguate_revision(value): | 
|  | """Returns True if `value` is a revision, False if it is a file, or dies.""" | 
|  | # If `value` is ambiguous (neither a commit nor a file), the following | 
|  | # command will die with an appropriate error message. | 
|  | run('git', 'rev-parse', value, verbose=False) | 
|  | object_type = get_object_type(value) | 
|  | if object_type is None: | 
|  | return False | 
|  | if object_type in ('commit', 'tag'): | 
|  | return True | 
|  | die('`%s` is a %s, but a commit or filename was expected' % | 
|  | (value, object_type)) | 
|  |  | 
|  |  | 
|  | def get_object_type(value): | 
|  | """Returns a string description of an object's type, or None if it is not | 
|  | a valid git object.""" | 
|  | cmd = ['git', 'cat-file', '-t', value] | 
|  | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
|  | stdout, stderr = p.communicate() | 
|  | if p.returncode != 0: | 
|  | return None | 
|  | return stdout.strip() | 
|  |  | 
|  |  | 
|  | def compute_diff_and_extract_lines(commits, files): | 
|  | """Calls compute_diff() followed by extract_lines().""" | 
|  | diff_process = compute_diff(commits, files) | 
|  | changed_lines = extract_lines(diff_process.stdout) | 
|  | diff_process.stdout.close() | 
|  | diff_process.wait() | 
|  | if diff_process.returncode != 0: | 
|  | # Assume error was already printed to stderr. | 
|  | sys.exit(2) | 
|  | return changed_lines | 
|  |  | 
|  |  | 
|  | def compute_diff(commits, files): | 
|  | """Return a subprocess object producing the diff from `commits`. | 
|  |  | 
|  | The return value's `stdin` file object will produce a patch with the | 
|  | differences between the working directory and the first commit if a single | 
|  | one was specified, or the difference between both specified commits, filtered | 
|  | on `files` (if non-empty).  Zero context lines are used in the patch.""" | 
|  | git_tool = 'diff-index' | 
|  | if len(commits) > 1: | 
|  | git_tool = 'diff-tree' | 
|  | cmd = ['git', git_tool, '-p', '-U0'] + commits + ['--'] | 
|  | cmd.extend(files) | 
|  | p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) | 
|  | p.stdin.close() | 
|  | return p | 
|  |  | 
|  |  | 
|  | def extract_lines(patch_file): | 
|  | """Extract the changed lines in `patch_file`. | 
|  |  | 
|  | The return value is a dictionary mapping filename to a list of (start_line, | 
|  | line_count) pairs. | 
|  |  | 
|  | The input must have been produced with ``-U0``, meaning unidiff format with | 
|  | zero lines of context.  The return value is a dict mapping filename to a | 
|  | list of line `Range`s.""" | 
|  | matches = {} | 
|  | for line in patch_file: | 
|  | match = re.search(r'^\+\+\+\ [^/]+/(.*)', line) | 
|  | if match: | 
|  | filename = match.group(1).rstrip('\r\n') | 
|  | match = re.search(r'^@@ -[0-9,]+ \+(\d+)(,(\d+))?', line) | 
|  | if match: | 
|  | start_line = int(match.group(1)) | 
|  | line_count = 1 | 
|  | if match.group(3): | 
|  | line_count = int(match.group(3)) | 
|  | if line_count > 0: | 
|  | matches.setdefault(filename, []).append(Range(start_line, line_count)) | 
|  | return matches | 
|  |  | 
|  |  | 
|  | def filter_by_extension(dictionary, allowed_extensions): | 
|  | """Delete every key in `dictionary` that doesn't have an allowed extension. | 
|  |  | 
|  | `allowed_extensions` must be a collection of lowercase file extensions, | 
|  | excluding the period.""" | 
|  | allowed_extensions = frozenset(allowed_extensions) | 
|  | for filename in list(dictionary.keys()): | 
|  | base_ext = filename.rsplit('.', 1) | 
|  | if len(base_ext) == 1 or base_ext[1].lower() not in allowed_extensions: | 
|  | del dictionary[filename] | 
|  |  | 
|  |  | 
|  | def cd_to_toplevel(): | 
|  | """Change to the top level of the git repository.""" | 
|  | toplevel = run('git', 'rev-parse', '--show-toplevel') | 
|  | os.chdir(toplevel) | 
|  |  | 
|  |  | 
|  | def create_tree_from_workdir(filenames): | 
|  | """Create a new git tree with the given files from the working directory. | 
|  |  | 
|  | Returns the object ID (SHA-1) of the created tree.""" | 
|  | return create_tree(filenames, '--stdin') | 
|  |  | 
|  |  | 
|  | def run_clang_format_and_save_to_tree(changed_lines, revision=None, | 
|  | binary='clang-format', style=None): | 
|  | """Run clang-format on each file and save the result to a git tree. | 
|  |  | 
|  | Returns the object ID (SHA-1) of the created tree.""" | 
|  | def iteritems(container): | 
|  | try: | 
|  | return container.iteritems() # Python 2 | 
|  | except AttributeError: | 
|  | return container.items() # Python 3 | 
|  | def index_info_generator(): | 
|  | for filename, line_ranges in iteritems(changed_lines): | 
|  | if revision: | 
|  | git_metadata_cmd = ['git', 'ls-tree', | 
|  | '%s:%s' % (revision, os.path.dirname(filename)), | 
|  | os.path.basename(filename)] | 
|  | git_metadata = subprocess.Popen(git_metadata_cmd, stdin=subprocess.PIPE, | 
|  | stdout=subprocess.PIPE) | 
|  | stdout = git_metadata.communicate()[0] | 
|  | mode = oct(int(stdout.split()[0], 8)) | 
|  | else: | 
|  | mode = oct(os.stat(filename).st_mode) | 
|  | # Adjust python3 octal format so that it matches what git expects | 
|  | if mode.startswith('0o'): | 
|  | mode = '0' + mode[2:] | 
|  | blob_id = clang_format_to_blob(filename, line_ranges, | 
|  | revision=revision, | 
|  | binary=binary, | 
|  | style=style) | 
|  | yield '%s %s\t%s' % (mode, blob_id, filename) | 
|  | return create_tree(index_info_generator(), '--index-info') | 
|  |  | 
|  |  | 
|  | def create_tree(input_lines, mode): | 
|  | """Create a tree object from the given input. | 
|  |  | 
|  | If mode is '--stdin', it must be a list of filenames.  If mode is | 
|  | '--index-info' is must be a list of values suitable for "git update-index | 
|  | --index-info", such as "<mode> <SP> <sha1> <TAB> <filename>".  Any other mode | 
|  | is invalid.""" | 
|  | assert mode in ('--stdin', '--index-info') | 
|  | cmd = ['git', 'update-index', '--add', '-z', mode] | 
|  | with temporary_index_file(): | 
|  | p = subprocess.Popen(cmd, stdin=subprocess.PIPE) | 
|  | for line in input_lines: | 
|  | p.stdin.write('%s\0' % line) | 
|  | p.stdin.close() | 
|  | if p.wait() != 0: | 
|  | die('`%s` failed' % ' '.join(cmd)) | 
|  | tree_id = run('git', 'write-tree') | 
|  | return tree_id | 
|  |  | 
|  |  | 
|  | def clang_format_to_blob(filename, line_ranges, revision=None, | 
|  | binary='clang-format', style=None): | 
|  | """Run clang-format on the given file and save the result to a git blob. | 
|  |  | 
|  | Runs on the file in `revision` if not None, or on the file in the working | 
|  | directory if `revision` is None. | 
|  |  | 
|  | Returns the object ID (SHA-1) of the created blob.""" | 
|  | clang_format_cmd = [binary] | 
|  | if style: | 
|  | clang_format_cmd.extend(['-style='+style]) | 
|  | clang_format_cmd.extend([ | 
|  | '-lines=%s:%s' % (start_line, start_line+line_count-1) | 
|  | for start_line, line_count in line_ranges]) | 
|  | if revision: | 
|  | clang_format_cmd.extend(['-assume-filename='+filename]) | 
|  | git_show_cmd = ['git', 'cat-file', 'blob', '%s:%s' % (revision, filename)] | 
|  | git_show = subprocess.Popen(git_show_cmd, stdin=subprocess.PIPE, | 
|  | stdout=subprocess.PIPE) | 
|  | git_show.stdin.close() | 
|  | clang_format_stdin = git_show.stdout | 
|  | else: | 
|  | clang_format_cmd.extend([filename]) | 
|  | git_show = None | 
|  | clang_format_stdin = subprocess.PIPE | 
|  | try: | 
|  | clang_format = subprocess.Popen(clang_format_cmd, stdin=clang_format_stdin, | 
|  | stdout=subprocess.PIPE) | 
|  | if clang_format_stdin == subprocess.PIPE: | 
|  | clang_format_stdin = clang_format.stdin | 
|  | except OSError as e: | 
|  | if e.errno == errno.ENOENT: | 
|  | die('cannot find executable "%s"' % binary) | 
|  | else: | 
|  | raise | 
|  | clang_format_stdin.close() | 
|  | hash_object_cmd = ['git', 'hash-object', '-w', '--path='+filename, '--stdin'] | 
|  | hash_object = subprocess.Popen(hash_object_cmd, stdin=clang_format.stdout, | 
|  | stdout=subprocess.PIPE) | 
|  | clang_format.stdout.close() | 
|  | stdout = hash_object.communicate()[0] | 
|  | if hash_object.returncode != 0: | 
|  | die('`%s` failed' % ' '.join(hash_object_cmd)) | 
|  | if clang_format.wait() != 0: | 
|  | die('`%s` failed' % ' '.join(clang_format_cmd)) | 
|  | if git_show and git_show.wait() != 0: | 
|  | die('`%s` failed' % ' '.join(git_show_cmd)) | 
|  | return stdout.rstrip('\r\n') | 
|  |  | 
|  |  | 
|  | @contextlib.contextmanager | 
|  | def temporary_index_file(tree=None): | 
|  | """Context manager for setting GIT_INDEX_FILE to a temporary file and deleting | 
|  | the file afterward.""" | 
|  | index_path = create_temporary_index(tree) | 
|  | old_index_path = os.environ.get('GIT_INDEX_FILE') | 
|  | os.environ['GIT_INDEX_FILE'] = index_path | 
|  | try: | 
|  | yield | 
|  | finally: | 
|  | if old_index_path is None: | 
|  | del os.environ['GIT_INDEX_FILE'] | 
|  | else: | 
|  | os.environ['GIT_INDEX_FILE'] = old_index_path | 
|  | os.remove(index_path) | 
|  |  | 
|  |  | 
|  | def create_temporary_index(tree=None): | 
|  | """Create a temporary index file and return the created file's path. | 
|  |  | 
|  | If `tree` is not None, use that as the tree to read in.  Otherwise, an | 
|  | empty index is created.""" | 
|  | gitdir = run('git', 'rev-parse', '--git-dir') | 
|  | path = os.path.join(gitdir, temp_index_basename) | 
|  | if tree is None: | 
|  | tree = '--empty' | 
|  | run('git', 'read-tree', '--index-output='+path, tree) | 
|  | return path | 
|  |  | 
|  |  | 
|  | def print_diff(old_tree, new_tree): | 
|  | """Print the diff between the two trees to stdout.""" | 
|  | # We use the porcelain 'diff' and not plumbing 'diff-tree' because the output | 
|  | # is expected to be viewed by the user, and only the former does nice things | 
|  | # like color and pagination. | 
|  | # | 
|  | # We also only print modified files since `new_tree` only contains the files | 
|  | # that were modified, so unmodified files would show as deleted without the | 
|  | # filter. | 
|  | subprocess.check_call(['git', 'diff', '--diff-filter=M', old_tree, new_tree, | 
|  | '--']) | 
|  |  | 
|  |  | 
|  | def apply_changes(old_tree, new_tree, force=False, patch_mode=False): | 
|  | """Apply the changes in `new_tree` to the working directory. | 
|  |  | 
|  | Bails if there are local changes in those files and not `force`.  If | 
|  | `patch_mode`, runs `git checkout --patch` to select hunks interactively.""" | 
|  | changed_files = run('git', 'diff-tree', '--diff-filter=M', '-r', '-z', | 
|  | '--name-only', old_tree, | 
|  | new_tree).rstrip('\0').split('\0') | 
|  | if not force: | 
|  | unstaged_files = run('git', 'diff-files', '--name-status', *changed_files) | 
|  | if unstaged_files: | 
|  | print('The following files would be modified but ' | 
|  | 'have unstaged changes:', file=sys.stderr) | 
|  | print(unstaged_files, file=sys.stderr) | 
|  | print('Please commit, stage, or stash them first.', file=sys.stderr) | 
|  | sys.exit(2) | 
|  | if patch_mode: | 
|  | # In patch mode, we could just as well create an index from the new tree | 
|  | # and checkout from that, but then the user will be presented with a | 
|  | # message saying "Discard ... from worktree".  Instead, we use the old | 
|  | # tree as the index and checkout from new_tree, which gives the slightly | 
|  | # better message, "Apply ... to index and worktree".  This is not quite | 
|  | # right, since it won't be applied to the user's index, but oh well. | 
|  | with temporary_index_file(old_tree): | 
|  | subprocess.check_call(['git', 'checkout', '--patch', new_tree]) | 
|  | index_tree = old_tree | 
|  | else: | 
|  | with temporary_index_file(new_tree): | 
|  | run('git', 'checkout-index', '-a', '-f') | 
|  | return changed_files | 
|  |  | 
|  |  | 
|  | def run(*args, **kwargs): | 
|  | stdin = kwargs.pop('stdin', '') | 
|  | verbose = kwargs.pop('verbose', True) | 
|  | strip = kwargs.pop('strip', True) | 
|  | for name in kwargs: | 
|  | raise TypeError("run() got an unexpected keyword argument '%s'" % name) | 
|  | p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | 
|  | stdin=subprocess.PIPE) | 
|  | stdout, stderr = p.communicate(input=stdin) | 
|  | if p.returncode == 0: | 
|  | if stderr: | 
|  | if verbose: | 
|  | print('`%s` printed to stderr:' % ' '.join(args), file=sys.stderr) | 
|  | print(stderr.rstrip(), file=sys.stderr) | 
|  | if strip: | 
|  | stdout = stdout.rstrip('\r\n') | 
|  | return stdout | 
|  | if verbose: | 
|  | print('`%s` returned %s' % (' '.join(args), p.returncode), file=sys.stderr) | 
|  | if stderr: | 
|  | print(stderr.rstrip(), file=sys.stderr) | 
|  | sys.exit(2) | 
|  |  | 
|  |  | 
|  | def die(message): | 
|  | print('error:', message, file=sys.stderr) | 
|  | sys.exit(2) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main() |