1"""An object-oriented interface to .netrc files."""
2
3# Module and documentation by Eric S. Raymond, 21 Dec 1998
4
5import os, shlex
6
7__all__ = ["netrc", "NetrcParseError"]
8
9
10class NetrcParseError(Exception):
11    """Exception raised on syntax errors in the .netrc file."""
12    def __init__(self, msg, filename=None, lineno=None):
13        self.filename = filename
14        self.lineno = lineno
15        self.msg = msg
16        Exception.__init__(self, msg)
17
18    def __str__(self):
19        return "%s (%s, line %s)" % (self.msg, self.filename, self.lineno)
20
21
22class netrc:
23    def __init__(self, file=None):
24        if file is None:
25            try:
26                file = os.path.join(os.environ['HOME'], ".netrc")
27            except KeyError:
28                raise IOError("Could not find .netrc: $HOME is not set")
29        self.hosts = {}
30        self.macros = {}
31        with open(file) as fp:
32            self._parse(file, fp)
33
34    def _parse(self, file, fp):
35        lexer = shlex.shlex(fp)
36        lexer.wordchars += r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
37        lexer.commenters = lexer.commenters.replace('#', '')
38        while 1:
39            # Look for a machine, default, or macdef top-level keyword
40            toplevel = tt = lexer.get_token()
41            if not tt:
42                break
43            elif tt[0] == '#':
44                # seek to beginning of comment, in case reading the token put
45                # us on a new line, and then skip the rest of the line.
46                pos = len(tt) + 1
47                lexer.instream.seek(-pos, 1)
48                lexer.instream.readline()
49                continue
50            elif tt == 'machine':
51                entryname = lexer.get_token()
52            elif tt == 'default':
53                entryname = 'default'
54            elif tt == 'macdef':                # Just skip to end of macdefs
55                entryname = lexer.get_token()
56                self.macros[entryname] = []
57                lexer.whitespace = ' \t'
58                while 1:
59                    line = lexer.instream.readline()
60                    if not line or line == '\012':
61                        lexer.whitespace = ' \t\r\n'
62                        break
63                    self.macros[entryname].append(line)
64                continue
65            else:
66                raise NetrcParseError(
67                    "bad toplevel token %r" % tt, file, lexer.lineno)
68
69            # We're looking at start of an entry for a named machine or default.
70            login = ''
71            account = password = None
72            self.hosts[entryname] = {}
73            while 1:
74                tt = lexer.get_token()
75                if (tt.startswith('#') or
76                    tt in {'', 'machine', 'default', 'macdef'}):
77                    if password:
78                        self.hosts[entryname] = (login, account, password)
79                        lexer.push_token(tt)
80                        break
81                    else:
82                        raise NetrcParseError(
83                            "malformed %s entry %s terminated by %s"
84                            % (toplevel, entryname, repr(tt)),
85                            file, lexer.lineno)
86                elif tt == 'login' or tt == 'user':
87                    login = lexer.get_token()
88                elif tt == 'account':
89                    account = lexer.get_token()
90                elif tt == 'password':
91                    password = lexer.get_token()
92                else:
93                    raise NetrcParseError("bad follower token %r" % tt,
94                                          file, lexer.lineno)
95
96    def authenticators(self, host):
97        """Return a (user, account, password) tuple for given host."""
98        if host in self.hosts:
99            return self.hosts[host]
100        elif 'default' in self.hosts:
101            return self.hosts['default']
102        else:
103            return None
104
105    def __repr__(self):
106        """Dump the class data in the format of a .netrc file."""
107        rep = ""
108        for host in self.hosts.keys():
109            attrs = self.hosts[host]
110            rep = rep + "machine "+ host + "\n\tlogin " + repr(attrs[0]) + "\n"
111            if attrs[1]:
112                rep = rep + "account " + repr(attrs[1])
113            rep = rep + "\tpassword " + repr(attrs[2]) + "\n"
114        for macro in self.macros.keys():
115            rep = rep + "macdef " + macro + "\n"
116            for line in self.macros[macro]:
117                rep = rep + line
118            rep = rep + "\n"
119        return rep
120
121if __name__ == '__main__':
122    print netrc()
123