I wrote a script that updates some library files for the game framework libgdx by grabbing the latest nightly build .zip file from a server and extracting the contents to the appropriate locations.#!/usr/bin/python__appname__ = 'libgdx_library_updater'__version__ = 0.1__author__ = Jon Renner <rennerjc@gmail.com>__url__ = http://github.com/jrenner/libgdx-updater__licence__ = MITimport os, time, sys, urllib2, re, datetime, tempfile, zipfile, argparse# error handling functions and utilsdef fatal_error(msg):    print ERROR: %s % msg    sys.exit(1)def warning_error(msg):    print WARNING: %s % msg    if not FORCE:        answer = confirm(abort? (Y/n): )        if answer in YES:            fatal_error(USER QUIT)            def confirm(msg):    answer = raw_input(msg)    return answer.lower()def human_time(t):    minutes = t / 60    seconds = t % 60    return %.0fm %.1fs % (minutes, seconds)    # constantsYES = ['y', 'ye', 'yes', '']# for finding the time of the latest nightly build from the web page htmlDATE_RE = r[0-9]{1,2}-[A-Za-z]{3,4}-[0-9]{4}\s[0-9]+:[0-9]+REMOTE_DATE_FORMAT = %d-%b-%Y %H:%MSUPPORTED_PLATFORMS = ['android', 'desktop', 'gwt']CORE_LIBS = [gdx.jar,             gdx-sources.jar]DESKTOP_LIBS = [gdx-backend-lwjgl.jar,                gdx-backend-lwjgl-natives.jar,                gdx-natives.jar] ANDROID_LIBS = [gdx-backend-android.jar,                armeabi/libgdx.so,                armeabi/libandroidgl20.so,                armeabi-v7a/libgdx.so,                armeabi-v7a/libandroidgl20.so]GWT_LIBS = [gdx-backend-gwt.jar]                # parse argumentsEPILOGUE_TEXT = %s\n%s % (__author__, __url__) + \nUSE AT YOUR OWN RISK!parser = argparse.ArgumentParser(description='LibGDX Library Updater %s' % __version__, epilog=EPILOGUE_TEXT)parser.add_argument('-d', '--directory', help='set the libgdx project/workspace directory', default=os.getcwd())parser.add_argument('-i', '--interactive', action='store_true', help='ask for confirmation for every file', default=False)parser.add_argument('-f', '--force-update', action='store_true', help='no confirmations, just update without checking nightly\'s datetime', default=False)parser.add_argument('-a', '--archive', help='specify libgdx zip file to use for update', default=None)args = parser.parse_args()PROJECT_DIR = args.directoryINTERACTIVE = args.interactiveFORCE = args.force_updateARCHIVE = args.archive# mutually exclusiveif FORCE:    INTERACTIVE = False# check the time of the latest archive on the nightlies serverdef get_remote_archive_mtime():    index_page = urllib2.urlopen(http://libgdx.badlogicgames.com/nightlies/)    contents = index_page.read()    print -- OK --    # regex for filename    regex = rlibgdx-nightly-latest\.zip    # add regex for anything followed by the nighlty html time format    regex += r.*%s % DATE_RE    try:        result = re.findall(regex, contents)[0]    except IndexError as e:        print REGEX ERROR: failed to find '%s' in:\n%s % (regex, contents)        fatal_error(regex failure to match)    try:        mtime = re.findall(DATE_RE, result)[0]    except IndexError as e:        print REGEX ERROR: failed to find datetime in: %s % result        fatal_error(regex failure to match)    dtime = datetime.datetime.strptime(mtime, REMOTE_DATE_FORMAT)    return dtime# downloads and returns a temporary file contained the latest nightly archivedef download_libgdx_zip():    libgdx = tempfile.TemporaryFile()    url = http://libgdx.badlogicgames.com/nightlies/libgdx-nightly-latest.zip    # testing url - don't hammer badlogic server, host the file on localhost instead    # url = http://localhost/libgdx-nightly-latest.zip    resp = urllib2.urlopen(url)    print downloading file: %s % url    total_size = resp.info().getheader('Content-Length').strip()    total_size = int(total_size)        # base 10 SI units - following Ubuntu policy because it makes sense - https://wiki.ubuntu.com/UnitsPolicy    total_size_megabytes = total_size / 1000000.0    bytes_read = 0    chunk_size = 10000 # 10kB per chunk    while True:                chunk = resp.read(chunk_size)        libgdx.write(chunk)        bytes_read += len(chunk)                bytes_read_megabytes = bytes_read / 1000000.0        percent = (bytes_read / float(total_size)) * 100        sys.stdout.write(\rprogress: {:>8}{:.2f} / {:.2f} mB ({:.0f}% complete).format(            , bytes_read_megabytes, total_size_megabytes, percent))        sys.stdout.flush()        if bytes_read >= total_size:            print finished download            break    return libgdxdef update_files(libs, locations, archive):        for lib in libs:                if lib in archive.namelist():                        if INTERACTIVE:                answer = confirm(overwrite %s? (Y/n):  % lib)                if answer not in YES:                                        print skipped: %s % lib                            continue            with archive.open(lib, r) as fin:                filename = os.path.basename(lib)                final_path = os.path.join(locations[lib], filename)                with open(final_path, w) as fout:                    fout.write(fin.read())                                    print extracted to %s % final_pathdef run_core(locations, archive):    title(CORE)    update_files(CORE_LIBS, locations, archive)def run_android(locations, archive):    title(ANDROID)    update_files(ANDROID_LIBS, locations, archive)    def run_desktop(locations, archive):    title(DESKTOP)    update_files(DESKTOP_LIBS, locations, archive)    def run_gwt(locations, archive):    title(GWT)    update_files(GWT_LIBS, locations, archive)def search_for_lib_locations(directory):        platforms = []    search_list = CORE_LIBS + DESKTOP_LIBS + ANDROID_LIBS    locations = {}        for element in search_list:        locations[element] = None    for (this_dir, dirs, files) in os.walk(directory):                for element in search_list:            split_path = os.path.split(element)            path = os.path.split(split_path[0])[-1]            filename = split_path[1]            for f in files:                match = False                if filename == f:                    f_dir = os.path.split(this_dir)[-1]                                        if path == :                        match = True                    else:                        if path == f_dir:                            match = True                if match:                    if locations[element] != None:                        print WARNING: found %s in more than one place! % element                        if not FORCE:                            answer = confirm(continue? (Y/n): )                            if answer not in YES:                                fatal_error(USER ABORT)                    locations[element] = this_dir                       for lib, loc in locations.items():        if loc == None:            print WARNING: did not find library %s in directory tree of: %s % (lib, directory)    found_libraries = [lib for lib, loc in locations.items() if locations[lib] != None]    if found_all_in_set(CORE_LIBS, found_libraries):        platforms.append(core)    if found_all_in_set(ANDROID_LIBS, found_libraries):        platforms.append(android)    if found_all_in_set(DESKTOP_LIBS, found_libraries):        platforms.append(desktop)    if found_all_in_set(GWT_LIBS, found_libraries):        platforms.append(gwt)    return platforms, locationsdef found_all_in_set(lib_set, found_list):    for lib in lib_set:        if lib not in found_list:            return False    return Truedef main():        start_time = time.time()    print finding local libraries in %s % PROJECT_DIR    platforms, locations = search_for_lib_locations(PROJECT_DIR)    if core not in platforms:        fatal_error(did not find CORE libraries %s in project directory tree % str(CORE_LIBS))    else:        print found CORE libraries    for supported in SUPPORTED_PLATFORMS:        if supported in platforms:            print found libraries for platform: %s % supported.upper()        else:            print WARNING: did not find libraries for platform: %s - WILL NOT UPDATE % supported.upper()    if ARCHIVE == None:        print checking latest nightly...        mtime = get_remote_archive_mtime()        print lastest nightly from server: %s % mtime        if not FORCE:            answer = confirm(replace local libraries with files from latest nightly?(Y/n): )                if answer not in YES:                fatal_error(USER QUIT)        libgdx = download_libgdx_zip()            else:        if not os.path.exists(ARCHIVE):            fatal_error(archive file not found: %s % ARCHIVE)        if not FORCE:            answer = confirm(replace local libraries with files from '%s'?(Y/n):  % os.path.basename(ARCHIVE))                if answer not in YES:                fatal_error(USER QUIT)        libgdx = open(ARCHIVE, r)    with zipfile.ZipFile(libgdx) as archive:        if core in platforms:                    run_core(locations, archive)        if desktop in platforms:            run_desktop(locations, archive)        if android in platforms:            run_android(locations, archive)        if gwt in platforms:            run_gwt(locations, archive)    duration = time.time() - start_time        print finished updates in %s % human_time(duration)    libgdx.close()def title(text):    dashes = - * 10    print dashes +  %s  % text + dashesif __name__ == __main__:    main()
Updating libgdx game framework library files
python;library;libgdx
Your code looks pretty good. Some notes:According to PEP8, imports should be written in separate lines.fatal_error: I'd probably write the signature this way: fatal_error(msg, code=1).INTERACTIVE -> interactive. According to PEP8, global variables should be written lower-case.That's an opinion: I prefer to write multi-line lists/dictionaries in JSON style. You save indentation space and the reordering of elements is straightforward (all at the meager cost of two lines):DESKTOP_LIBS = [    gdx-backend-lwjgl.jar,    gdx-backend-lwjgl-natives.jar,    gdx-natives.jar,] Functions download_libgdx_zip and search_for_lib_locations are written (unnecessarily IMO) in a very imperative fashion. I'd probably refactor it with a functional approach in mind. At least don't reuse the same variable name to hold different values (i.e. total_size), as that takes away the sacred mathematical meaning of =.Those functions run_xyz(locations, archive) look very similar, why not a unique run(platform, locations, archive).Function found_all_in_set can be written:def found_all_in_set(lib_set, found_list):    return all(lib in found_list for lib in lib_set)Or:def found_all_in_set(lib_set, found_list):    return set(lib_list).issubset(set(found_list))if ARCHIVE == None: -> if ARCHIVE is None: although I prefer the (almost) equivalent, more declarative if not ARCHIVE:.There are a lot of imperative snippets that could be written functionally, for example this simple list-comprehension replaces a dozen lines from your code:platforms = [platform for (platform, libs) in zip(platforms, libs_list)    if found_all_in_set(libs, found_libraries)]