Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 2a13eb742c72e120ec902000b0d7140a7b34a396 @ojii committed Mar 20, 2012
Showing with 273 additions and 0 deletions.
  1. +24 −0 LICENSE.txt
  2. +42 −0 README.rst
  3. +1 −0 favssh/__init__.py
  4. +116 −0 favssh/config.py
  5. +49 −0 favssh/main.py
  6. +41 −0 setup.py
@@ -0,0 +1,24 @@
+Copyright (c) 2012, Jonas Obrist
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Jonas Obrist nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL JONAS OBRIST BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,42 @@
+############################
+ssh config command line tool
+############################
+
+``favssh`` is a tool to manipulate the ssh configuration file from the command
+line.
+
+
+************
+Installation
+************
+
+
+``pip install favssh`` in a virtualenv or ``sudo pip install favssh``.
+
+
+*****
+Usage
+*****
+
+List hosts
+==========
+
+``favssh [-c/--config-file=~/.ssh/config] list``
+
+
+Add host
+========
+
+``favssh [-c/--config-file=~/.ssh/config] add <name> <hostname> [-p/--port=22] [-u/--user=$USER]``
+
+
+Update host
+===========
+
+``favssh [-c/--config-file=~/.ssh/config] update <name> <key> <value>``
+
+
+Remove host
+===========
+
+``favssh [-c/--config-file=~/.ssh/config] remove <name>``
@@ -0,0 +1 @@
+__version__ = '1.0'
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+import os
+
+
+class Host(object):
+ def __init__(self, name, configuration, start=None, end=None):
+ self.name = name
+ self.configuration = configuration
+ self.start = start
+ self.end = end
+
+ def __str__(self):
+ return ''.join(self.get_lines())
+
+ def update(self, key, value):
+ self.configuration[key.lower()] = value
+
+ def get_lines(self):
+ lines = ['Host %s\n' % self.name]
+ for key, value in sorted(self.configuration.items()):
+ lines.append(' %s %s\n' % (key, value))
+ return lines
+
+
+class Configuration(object):
+ def __init__(self, path):
+ self.path = path
+ self.hosts = {}
+ self.delete_cache = []
+ self.lines = []
+ if os.path.exists(self.path):
+ self.read()
+
+ @classmethod
+ def from_argument(cls, path):
+ return cls(os.path.expanduser(path))
+
+ def read(self):
+ with open(self.path) as fobj:
+ current_host = None
+ current_host_start = 0
+ current_config = {}
+ # strip whitespace and remove empty lines
+ for index, line in enumerate(fobj.readlines()):
+ self.lines.append(line)
+ line = line.strip()
+ # comment
+ if line.startswith('#'):
+ continue
+ # comment
+ elif not line:
+ continue
+ # key = value style
+ elif '=' in line:
+ key, value = map(str.strip, line.split('=', 1))
+ # key value style
+ else:
+ key, value = map(str.strip, line.split(' ', 1))
+ # lower keys
+ key = key.lower()
+ # it's a host!
+ if key == 'host':
+ if current_host:
+ host = Host(current_host, current_config, current_host_start, index)
+ self.hosts[current_host] = host
+ current_config = {}
+ current_host_start = index
+ current_host = value
+ # it's a random other config!
+ else:
+ current_config[key] = value
+ if current_host:
+ host = Host(current_host, current_config, current_host_start, index + 1)
+ self.hosts[current_host] = host
+
+ def all_hosts(self):
+ return sorted(self.hosts.values(), key=lambda obj: getattr(obj, 'name'))
+
+ def add_host(self, host, **config):
+ if host in self.hosts:
+ raise Exception("Host %r is already in config, use favssh update")
+ else:
+ self.hosts[host] = Host(host, config)
+
+ def update_host(self, host, key, value):
+ if host in self.hosts:
+ self.hosts[host].update(key, value)
+ else:
+ raise Exception('Host %r is not in config, use favssh add' % host)
+
+ def remove_host(self, host):
+ if host in self.hosts:
+ self.delete_cache.append((self.hosts[host].start, self.hosts[host].end))
+ del self.hosts[host]
+ else:
+ raise Exception('Host %r is not in config' % host)
+
+ def write(self):
+ lines = list(self.lines)
+ def write_host(host):
+ if host.start:
+ lines[host.start:host.end] = host.get_lines()
+ else:
+ for line in host.get_lines():
+ lines.append(line)
+ def remove_host(start, end):
+ lines[start:end] = []
+ mutations = []
+ for host in self.hosts.values():
+ mutations.append((host.start, write_host, (host,)))
+ for start, end in self.delete_cache:
+ mutations.append((start, remove_host, (start, end)))
+ for _, action, args in sorted(mutations, reverse=True):
+ action(*args)
+ with open(self.path, 'w') as fobj:
+ fobj.writelines(lines)
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+from favssh.config import Configuration
+import os
+
+def command_list(args):
+ for host in args.config.all_hosts():
+ print host
+
+def command_add(args):
+ args.config.add_host(args.host, hostname=args.hostname, user=args.user, port=args.port)
+ args.config.write()
+
+def command_remove(args):
+ args.config.remove_host(args.host)
+ args.config.write()
+
+def command_update(args):
+ args.config.update_host(args.host, args.key, args.value)
+ args.config.write()
+
+def main():
+ import argparse
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-c', '--config-file', dest='config',
+ default='~/.ssh/config', type=Configuration.from_argument)
+ subparsers = parser.add_subparsers()
+
+ parser_list = subparsers.add_parser('list')
+ parser_list.set_defaults(func=command_list)
+
+ parser_add = subparsers.add_parser('add')
+ parser_add.add_argument('host')
+ parser_add.add_argument('hostname')
+ parser_add.add_argument('-u', '--user', default=os.getlogin())
+ parser_add.add_argument('-p', '--port', default=22, type=int)
+ parser_add.set_defaults(func=command_add)
+
+ parser_remove = subparsers.add_parser('remove')
+ parser_remove.add_argument('host')
+ parser_remove.set_defaults(func=command_remove)
+
+ parser_add = subparsers.add_parser('update')
+ parser_add.add_argument('host')
+ parser_add.add_argument('key')
+ parser_add.add_argument('value')
+ parser_add.set_defaults(func=command_update)
+
+ args = parser.parse_args()
+ args.func(args)
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from setuptools import setup
+from favssh import __version__
+
+INSTALL_REQUIRES = []
+
+try:
+ import argparse
+except ImportError:
+ INSTALL_REQUIRES.append('argparse')
+
+CLASSIFIERS = [
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Console',
+ 'Framework :: Django',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Topic :: Software Development',
+]
+
+setup(
+ name='favssh',
+ version=__version__,
+ description='A command line tool to manipulate ssh config files',
+ author='Jonas Obrist',
+ author_email='ojiidotch@gmail.com',
+ url='https://github.com/ojii/favssh/',
+ packages=['favssh'],
+ license='BSD',
+ platforms=['OS Independent'],
+ install_requires=INSTALL_REQUIRES,
+ entry_points="""
+ [console_scripts]
+ favssh = favssh.main:main
+ """,
+)

0 comments on commit 2a13eb7

Please sign in to comment.