From c22df4490027a62268f1d46073d9513aad83ef2a Mon Sep 17 00:00:00 2001
From: Robey Pointer <robey@lag.net>
Date: Tue, 27 Jun 2006 21:59:19 -0700
Subject: [PATCH] [project @ robey@lag.net-20060628045919-ffac82c51c51b3df]
 make HostKeys use odict to ensure order is preserved, and add HostKeys.save()

---
 paramiko/hostkeys.py | 27 ++++++++++++++++---
 paramiko/odict.py    | 63 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 87 insertions(+), 3 deletions(-)
 create mode 100644 paramiko/odict.py

diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py
index 5d88084..7cc8d40 100644
--- a/paramiko/hostkeys.py
+++ b/paramiko/hostkeys.py
@@ -26,6 +26,7 @@ import UserDict
 
 from paramiko.common import *
 from paramiko.dsskey import DSSKey
+from paramiko.odict import odict
 from paramiko.rsakey import RSAKey
 
 
@@ -50,7 +51,7 @@ class HostKeys (UserDict.DictMixin):
         @type filename: str
         """
         # hostname -> keytype -> PKey
-        self._keys = {}
+        self._keys = odict()
         self.contains_hashes = False
         if filename is not None:
             self.load(filename)
@@ -68,7 +69,7 @@ class HostKeys (UserDict.DictMixin):
         @type key: L{PKey}
         """
         if not hostname in self._keys:
-            self._keys[hostname] = {}
+            self._keys[hostname] = odict()
         if hostname.startswith('|1|'):
             self.contains_hashes = True
         self._keys[hostname][keytype] = key
@@ -89,7 +90,7 @@ class HostKeys (UserDict.DictMixin):
         
         @raise IOError: if there was an error reading the file
         """
-        f = file(filename, 'r')
+        f = open(filename, 'r')
         for line in f:
             line = line.strip()
             if (len(line) == 0) or (line[0] == '#'):
@@ -105,6 +106,26 @@ class HostKeys (UserDict.DictMixin):
                 elif keytype == 'ssh-dss':
                     self.add(host, keytype, DSSKey(data=base64.decodestring(key)))
         f.close()
+    
+    def save(self, filename):
+        """
+        Save host keys into a file, in the format used by openssh.  The order of
+        keys in the file will be preserved when possible (if these keys were
+        loaded from a file originally).  The single exception is that combined
+        lines will be split into individual key lines, which is arguably a bug.
+        
+        @param filename: name of the file to write
+        @type filename: str
+        
+        @raise IOError: if there was an error writing the file
+        
+        @since: 1.6.1
+        """
+        f = open(filename, 'w')
+        for hostname, d in self._keys.iteritems():
+            for keytype, key in d.iteritems():
+                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
+        f.close()
 
     def lookup(self, hostname):
         """
diff --git a/paramiko/odict.py b/paramiko/odict.py
new file mode 100644
index 0000000..103e52f
--- /dev/null
+++ b/paramiko/odict.py
@@ -0,0 +1,63 @@
+#
+# This file and source code are in the public domain.
+#
+
+class odict (dict):
+    """
+    A dictionary with ordered keys.  Based on the cookbook recipe at:
+        http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747
+    """
+    
+    def __init__(self, *larg, **kwarg):
+        self._keys = []
+        dict.__init__(self, *larg, **kwarg)
+    
+    def __delitem__(self, key):
+        dict.__delitem__(self, key)
+        self._keys.remove(key)
+    
+    def __setitem__(self, key, item):
+        dict.__setitem__(self, key, item)
+        if key not in self._keys:
+            self._keys.append(key)
+    
+    def clear(self):
+        dict.clear(self)
+        self._keys = []
+    
+    def copy(self):
+        od = odict(self)
+        return od
+
+    def items(self):
+        return zip(self._keys, self.values())
+    
+    def iteritems(self):
+        for k in self._keys:
+            yield k, self[k]
+    
+    def keys(self):
+        return self._keys[:]
+    
+    def popitem(self):
+        try:
+            key = self._keys[-1]
+        except IndexError:
+            raise KeyError('dictionary is empty')
+        
+        val = self[key]
+        del self[key]
+        
+        return (key, val)
+
+    def setdefault(self, key, failobj=None):
+        if key not in self._keys:
+            self._keys.append(key)
+        dict.setdefault(self, key, failobj)
+    
+    def update(self, d):
+        for key, item in d.items():
+            self.__setitem__(key, item)
+    
+    def values(self):
+        return map(self.get, self._keys)