22
33# Module and documentation by Eric S. Raymond, 21 Dec 1998 
44
5- import  os 
6- import  stat 
5+ import  os , stat 
76
87__all__  =  ["netrc" , "NetrcParseError" ]
98
@@ -23,41 +22,53 @@ def __str__(self):
2322class  _netrclex :
2423    def  __init__ (self , fp ):
2524        self .lineno  =  1 
26-         self .dontskip  =  False 
2725        self .instream  =  fp 
2826        self .whitespace  =  "\n \t \r  " 
2927        self .pushback  =  []
28+         self .char_pushback  =  []
3029
3130    def  _read_char (self ):
31+         if  self .char_pushback :
32+             return  self .char_pushback .pop (0 )
3233        ch  =  self .instream .read (1 )
3334        if  ch  ==  "\n " :
3435            self .lineno  +=  1 
3536        return  ch 
3637
38+     def  skip_blank_lines (self ):
39+         fiter  =  iter (self ._read_char , "" )
40+         for  ch  in  fiter :
41+             if  ch  ==  '\n ' :
42+                 self .lineno  +=  1 
43+             else :
44+                 self .char_pushback .append (ch )
45+                 return 
46+ 
3747    def  get_token (self ):
38-         self .dontskip  =  False 
3948        if  self .pushback :
4049            return  self .pushback .pop (0 )
4150        token  =  "" 
42-         enquoted  =  False 
43-         while  ch  :=  self ._read_char ():
44-             if  ch  ==  '\\ ' :
45-                 ch  =  self ._read_char ()
46-                 token  +=  ch 
51+         fiter  =  iter (self ._read_char , "" )
52+         for  ch  in  fiter :
53+             if  ch  in  self .whitespace :
4754                continue 
48-             if  ch  in  self .whitespace  and  not  enquoted :
49-                 if  token  ==  "" :
50-                     continue 
51-                 if  ch  ==  '\n ' :
52-                     self .dontskip  =  True 
53-                 return  token 
5455            if  ch  ==  '"' :
55-                 if  enquoted :
56-                     return  token 
57-                 enquoted  =  True 
58-                 continue 
56+                 for  ch  in  fiter :
57+                     if  ch  ==  '"' :
58+                         return  token 
59+                     elif  ch  ==  "\\ " :
60+                         ch  =  self ._read_char ()
61+                     token  +=  ch 
5962            else :
63+                 if  ch  ==  "\\ " :
64+                     ch  =  self ._read_char ()
6065                token  +=  ch 
66+                 for  ch  in  fiter :
67+                     if  ch  in  self .whitespace :
68+                         return  token 
69+                     elif  ch  ==  "\\ " :
70+                         ch  =  self ._read_char ()
71+                     token  +=  ch 
6172        return  token 
6273
6374    def  push_token (self , token ):
@@ -67,7 +78,7 @@ def push_token(self, token):
6778class  netrc :
6879    def  __init__ (self , file = None ):
6980        default_netrc  =  file  is  None 
70-         if  default_netrc :
81+         if  file   is   None :
7182            file  =  os .path .join (os .path .expanduser ("~" ), ".netrc" )
7283        self .hosts  =  {}
7384        self .macros  =  {}
@@ -82,15 +93,14 @@ def _parse(self, file, fp, default_netrc):
8293        lexer  =  _netrclex (fp )
8394        while  1 :
8495            # Look for a machine, default, or macdef top-level keyword 
85-             tt  =  lexer .get_token ()
96+             lexer .skip_blank_lines ()
97+             saved_lineno  =  lexer .lineno 
98+             toplevel  =  tt  =  lexer .get_token ()
8699            if  not  tt :
87100                break 
88101            elif  tt [0 ] ==  '#' :
89-                 # For top level tokens, we skip line if the # is followed 
90-                 # by a space / newline. Otherwise, we only skip the token. 
91-                 if  tt  ==  '#'  and  not  lexer .dontskip :
102+                 if  lexer .lineno  ==  saved_lineno  and  len (tt ) ==  1 :
92103                    lexer .instream .readline ()
93-                     lexer .lineno  +=  1 
94104                continue 
95105            elif  tt  ==  'machine' :
96106                entryname  =  lexer .get_token ()
@@ -101,7 +111,6 @@ def _parse(self, file, fp, default_netrc):
101111                self .macros [entryname ] =  []
102112                while  1 :
103113                    line  =  lexer .instream .readline ()
104-                     lexer .lineno  +=  1 
105114                    if  not  line :
106115                        raise  NetrcParseError (
107116                            "Macro definition missing null line terminator." ,
@@ -118,18 +127,20 @@ def _parse(self, file, fp, default_netrc):
118127                    "bad toplevel token %r"  %  tt , file , lexer .lineno )
119128
120129            if  not  entryname :
121-                 raise  NetrcParseError (
122-                     "missing %r name"  %  tt , file , lexer .lineno )
130+                 raise  NetrcParseError ("missing %r name"  %  tt , file , lexer .lineno )
123131
124132            # We're looking at start of an entry for a named machine or default. 
125133            login  =  account  =  password  =  '' 
126134            self .hosts [entryname ] =  {}
127135            while  1 :
136+                 # Trailing blank lines would break the checks that determine if the token 
137+                 # is the last one on its line. 
138+                 lexer .skip_blank_lines ()
139+                 prev_lineno  =  lexer .lineno 
128140                tt  =  lexer .get_token ()
129141                if  tt .startswith ('#' ):
130-                     if  not   lexer .dontskip :
142+                     if  lexer .lineno   ==   prev_lineno :
131143                        lexer .instream .readline ()
132-                         lexer .lineno  +=  1 
133144                    continue 
134145                if  tt  in  {'' , 'machine' , 'default' , 'macdef' }:
135146                    self .hosts [entryname ] =  (login , account , password )
@@ -170,7 +181,12 @@ def _security_check(self, fp, default_netrc, login):
170181
171182    def  authenticators (self , host ):
172183        """Return a (user, account, password) tuple for given host.""" 
173-         return  self .hosts .get (host , self .hosts .get ('default' ))
184+         if  host  in  self .hosts :
185+             return  self .hosts [host ]
186+         elif  'default'  in  self .hosts :
187+             return  self .hosts ['default' ]
188+         else :
189+             return  None 
174190
175191    def  __repr__ (self ):
176192        """Dump the class data in the format of a .netrc file.""" 
@@ -187,3 +203,6 @@ def __repr__(self):
187203                rep  +=  line 
188204            rep  +=  "\n " 
189205        return  rep 
206+ 
207+ if  __name__  ==  '__main__' :
208+     print (netrc ())
0 commit comments