Quick conversion from imported submodules to direct module access

Hi all,

It’s been annoying me that in my latest project the imports are just ugly

from .bge_data import (GameObject, CameraObject,
from .structs import (RigidBodyState, AnimationState)
from .behaviour_tree import BehaviourTree
from .configuration import load_configuration
from .enums import (PhysicsType, ShotType, CameraMode, AIState,
                    Axis, AnimationMode, AnimationBlend)
from .signals import (CollisionSignal, PlayerInputSignal,
                    PhysicsReplicatedSignal, PhysicsSingleUpdateSignal,
                    PhysicsSetSimulatedSignal, PhysicsUnsetSimulatedSignal,
                    ActorDamagedSignal, ActorKilledSignal, SetMoveTarget)
from .inputs import InputManager
from .utilities import falloff_fraction, progress_string
from .timer import ManualTimer
from .draw_tools import (draw_arrow, draw_circle,
                         draw_box, draw_square_pyramid)

from aud import Factory, device as AudioDevice
from bge import logic, types
from collections import namedtuple, OrderedDict

from math import pi, radians
from mathutils import Euler, Vector, Matrix
from network import (Replicable, Attribute, Roles, WorldInfo,
                     simulated, Netmodes, StaticValue, RequireNetmode,
                     TypeRegister, ReplicableUnregisteredSignal,
                     UpdateSignal, ConnectionInterface, ConnectionStatus,
                     profile, supply_data)
from os import path
from operator import gt as more_than
from functools import lru_cache

I wrote a quick tool to isolate these and convert them into normal imports, then accessing the submodule directly as an attribute of that module

from . import bge_data
from . import structs
from . import behaviour_tree
from . import configuration
from . import enums
from . import signals
from . import inputs
from . import utilities
from . import timer
from . import draw_tools

import aud
import bge
import collections

import math
import mathutils
import network
import os
import operator
import functools

Using this module:

import sys
import re

class Quit(Exception):

def all_indices_of(needle, haystack):
    index = 0
    while True:
        new_index = haystack.find(needle)
        if new_index == -1:
        shifted_index = new_index + len(needle)
        yield index + new_index

        haystack = haystack[shifted_index:]
        index += shifted_index

def format_sequence(line):
    return line.strip().replace(" ", "")

def attribute_import(module, attribute):
    return "{}.{}".format(module, attribute)

def get_modules(data):
    return [x.strip() for x in data.split(",") if x.strip()]

def parser(iterable, mode=0, data=None, key=None, relative=False,
           conversions=None, removed=None, imports=None):
    if conversions is None:
        conversions = {}
        removed = {}
        imports = {}

        line = next(iterable)

    except StopIteration:
        return conversions, removed, imports

    except TypeError:
        return parser(iter(iterable))

    # Ensure we have valid imports
    if (not ("from" in line or "import" in line)
        and key is None and line.strip()):
        return conversions, removed, imports

    if line.strip() and not "*" in line:
        if key is None:
            if "as" in line:
                print("Warning, import rename not handled: '{}'".format(line))
            if "." in line:
                shifted = line[line.find(".") + 1:]
                name = shifted[:shifted.find(' ')]
                relative = True

                shifted = line[line.find("from") + len("from") + 1:]
                name = shifted[:shifted.find(' ')]
                relative = False

            removed[line] = name
            imports[name] = relative

            following = line[line.find("import") + len("import") + 1:]

            if "(" in following:
                following = following[1:]
                key = name
                if ")" in following:
                    key = None
                    following = following[:following.find(")")]
                    conversions[name] = get_modules(following)
                    data = [format_sequence(following)]
                conversions[name] = get_modules(following)

            if ")" in line:
                until = line[:line.find(")")]
                import_list = get_modules(''.join(data))
                conversions[key] = import_list
                removed[line] = key
                key = None

                removed[line] = key

    return parser(iterable, mode, data, key,
        relative, conversions, removed, imports)

def finder(line, module):
    fmt = "{0}{1}{0}|(?<=\(){1}\)|{1}\(\)".format("\\b", module)
    finder = re.compile(fmt, flags=re.DOTALL)
    return [match.span() for match in finder.finditer(line)]

def create_newlines(data):
    conversions, removed, imports = parser(data)
    imported = []
    for line in data:
        if line in removed:
            module = removed[line]
            if not module in imported:
                relative = imports[module]
                yield ("from . " if relative else "") + "import {}

        for key, key_data in conversions.items():
            for module in key_data:
                found = finder(line, module)
                for i in range(len(found)):
                    change = attribute_import(key, module)
                    start, end = found[i]
                    line = line[:start] + change + line[end:]
                    # Recalculate matches (as we have modified line)
                    found = finder(line, module)

        yield line

with open("C:/test.py", "w") as target:
    with open("C:/file.txt") as source: