1"""Fix changes imports of urllib which are now incompatible.
2   This is rather similar to fix_imports, but because of the more
3   complex nature of the fixing for urllib, it has its own fixer.
4"""
5# Author: Nick Edds
6
7# Local imports
8from lib2to3.fixes.fix_imports import alternates, FixImports
9from lib2to3 import fixer_base
10from lib2to3.fixer_util import (Name, Comma, FromImport, Newline,
11                                find_indentation, Node, syms)
12
13MAPPING = {"urllib":  [
14                ("urllib.request",
15                    ["URLopener", "FancyURLopener", "urlretrieve",
16                     "_urlopener", "urlopen", "urlcleanup",
17                     "pathname2url", "url2pathname"]),
18                ("urllib.parse",
19                    ["quote", "quote_plus", "unquote", "unquote_plus",
20                     "urlencode", "splitattr", "splithost", "splitnport",
21                     "splitpasswd", "splitport", "splitquery", "splittag",
22                     "splittype", "splituser", "splitvalue", ]),
23                ("urllib.error",
24                    ["ContentTooShortError"])],
25           "urllib2" : [
26                ("urllib.request",
27                    ["urlopen", "install_opener", "build_opener",
28                     "Request", "OpenerDirector", "BaseHandler",
29                     "HTTPDefaultErrorHandler", "HTTPRedirectHandler",
30                     "HTTPCookieProcessor", "ProxyHandler",
31                     "HTTPPasswordMgr",
32                     "HTTPPasswordMgrWithDefaultRealm",
33                     "AbstractBasicAuthHandler",
34                     "HTTPBasicAuthHandler", "ProxyBasicAuthHandler",
35                     "AbstractDigestAuthHandler",
36                     "HTTPDigestAuthHandler", "ProxyDigestAuthHandler",
37                     "HTTPHandler", "HTTPSHandler", "FileHandler",
38                     "FTPHandler", "CacheFTPHandler",
39                     "UnknownHandler"]),
40                ("urllib.error",
41                    ["URLError", "HTTPError"]),
42           ]
43}
44
45# Duplicate the url parsing functions for urllib2.
46MAPPING["urllib2"].append(MAPPING["urllib"][1])
47
48
49def build_pattern():
50    bare = set()
51    for old_module, changes in MAPPING.items():
52        for change in changes:
53            new_module, members = change
54            members = alternates(members)
55            yield """import_name< 'import' (module=%r
56                                  | dotted_as_names< any* module=%r any* >) >
57                  """ % (old_module, old_module)
58            yield """import_from< 'from' mod_member=%r 'import'
59                       ( member=%s | import_as_name< member=%s 'as' any > |
60                         import_as_names< members=any*  >) >
61                  """ % (old_module, members, members)
62            yield """import_from< 'from' module_star=%r 'import' star='*' >
63                  """ % old_module
64            yield """import_name< 'import'
65                                  dotted_as_name< module_as=%r 'as' any > >
66                  """ % old_module
67            # bare_with_attr has a special significance for FixImports.match().
68            yield """power< bare_with_attr=%r trailer< '.' member=%s > any* >
69                  """ % (old_module, members)
70
71
72class FixUrllib(FixImports):
73
74    def build_pattern(self):
75        return "|".join(build_pattern())
76
77    def transform_import(self, node, results):
78        """Transform for the basic import case. Replaces the old
79           import name with a comma separated list of its
80           replacements.
81        """
82        import_mod = results.get("module")
83        pref = import_mod.prefix
84
85        names = []
86
87        # create a Node list of the replacement modules
88        for name in MAPPING[import_mod.value][:-1]:
89            names.extend([Name(name[0], prefix=pref), Comma()])
90        names.append(Name(MAPPING[import_mod.value][-1][0], prefix=pref))
91        import_mod.replace(names)
92
93    def transform_member(self, node, results):
94        """Transform for imports of specific module elements. Replaces
95           the module to be imported from with the appropriate new
96           module.
97        """
98        mod_member = results.get("mod_member")
99        pref = mod_member.prefix
100        member = results.get("member")
101
102        # Simple case with only a single member being imported
103        if member:
104            # this may be a list of length one, or just a node
105            if isinstance(member, list):
106                member = member[0]
107            new_name = None
108            for change in MAPPING[mod_member.value]:
109                if member.value in change[1]:
110                    new_name = change[0]
111                    break
112            if new_name:
113                mod_member.replace(Name(new_name, prefix=pref))
114            else:
115                self.cannot_convert(node, "This is an invalid module element")
116
117        # Multiple members being imported
118        else:
119            # a dictionary for replacements, order matters
120            modules = []
121            mod_dict = {}
122            members = results["members"]
123            for member in members:
124                # we only care about the actual members
125                if member.type == syms.import_as_name:
126                    as_name = member.children[2].value
127                    member_name = member.children[0].value
128                else:
129                    member_name = member.value
130                    as_name = None
131                if member_name != u",":
132                    for change in MAPPING[mod_member.value]:
133                        if member_name in change[1]:
134                            if change[0] not in mod_dict:
135                                modules.append(change[0])
136                            mod_dict.setdefault(change[0], []).append(member)
137
138            new_nodes = []
139            indentation = find_indentation(node)
140            first = True
141            def handle_name(name, prefix):
142                if name.type == syms.import_as_name:
143                    kids = [Name(name.children[0].value, prefix=prefix),
144                            name.children[1].clone(),
145                            name.children[2].clone()]
146                    return [Node(syms.import_as_name, kids)]
147                return [Name(name.value, prefix=prefix)]
148            for module in modules:
149                elts = mod_dict[module]
150                names = []
151                for elt in elts[:-1]:
152                    names.extend(handle_name(elt, pref))
153                    names.append(Comma())
154                names.extend(handle_name(elts[-1], pref))
155                new = FromImport(module, names)
156                if not first or node.parent.prefix.endswith(indentation):
157                    new.prefix = indentation
158                new_nodes.append(new)
159                first = False
160            if new_nodes:
161                nodes = []
162                for new_node in new_nodes[:-1]:
163                    nodes.extend([new_node, Newline()])
164                nodes.append(new_nodes[-1])
165                node.replace(nodes)
166            else:
167                self.cannot_convert(node, "All module elements are invalid")
168
169    def transform_dot(self, node, results):
170        """Transform for calls to module members in code."""
171        module_dot = results.get("bare_with_attr")
172        member = results.get("member")
173        new_name = None
174        if isinstance(member, list):
175            member = member[0]
176        for change in MAPPING[module_dot.value]:
177            if member.value in change[1]:
178                new_name = change[0]
179                break
180        if new_name:
181            module_dot.replace(Name(new_name,
182                                    prefix=module_dot.prefix))
183        else:
184            self.cannot_convert(node, "This is an invalid module element")
185
186    def transform(self, node, results):
187        if results.get("module"):
188            self.transform_import(node, results)
189        elif results.get("mod_member"):
190            self.transform_member(node, results)
191        elif results.get("bare_with_attr"):
192            self.transform_dot(node, results)
193        # Renaming and star imports are not supported for these modules.
194        elif results.get("module_star"):
195            self.cannot_convert(node, "Cannot handle star imports.")
196        elif results.get("module_as"):
197            self.cannot_convert(node, "This module is now multiple modules")
198