From f211bb6c74a994f3d0fc1ebc1d2c2450ea1247f2 Mon Sep 17 00:00:00 2001
From: ZyX <kp-pav@ya.ru>
Date: Mon, 18 Feb 2013 20:11:45 +0400
Subject: [PATCH] Add ability to override configuration options

Related changes:
- Moved all non-ASCII symbols into `segment_data`
- Added --config_path, replaced nargs='*' with better action='append'
- Added g:powerline_config_path vim option
- Added ipython overrides (via additional arguments to setup() or c.Powerline)

TODO: support for non-string scalars in vim overrides.

Fixes #231
---
 docs/source/configuration.rst                 | 78 ++++++++++++++++-
 powerline/__init__.py                         | 81 +++++++++++++++++
 powerline/bindings/ipython/post_0_11.py       | 19 +++-
 powerline/bindings/ipython/pre_0_11.py        | 14 ++-
 powerline/bindings/qtile/widget.py            |  2 +-
 powerline/bindings/vim/plugin/powerline.vim   |  8 +-
 powerline/bindings/zsh/__init__.py            | 22 ++++-
 .../config_files/themes/shell/default.json    | 26 ++++--
 .../themes/shell/default_leftonly.json        | 23 +++--
 .../config_files/themes/tmux/default.json     | 26 ++++--
 powerline/core.py                             | 86 -------------------
 powerline/ipython.py                          | 27 ++++++
 powerline/lib/__init__.py                     |  9 ++
 powerline/matcher.py                          | 13 ++-
 powerline/shell.py                            | 42 +++++++++
 powerline/vim.py                              | 72 ++++++++++++++++
 scripts/powerline                             | 24 +++++-
 17 files changed, 437 insertions(+), 135 deletions(-)
 delete mode 100644 powerline/core.py
 create mode 100644 powerline/ipython.py
 create mode 100644 powerline/shell.py
 create mode 100644 powerline/vim.py

diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst
index 25cf05fb..bbefffec 100644
--- a/docs/source/configuration.rst
+++ b/docs/source/configuration.rst
@@ -132,8 +132,8 @@ Common configuration is a subdictionary that is a value of ``common`` key in
 
 ``paths``
     Defines additional paths which will be searched for modules when using 
-    :ref:`module segment option <config-themes-seg-module>`. Paths defined 
-    here have priority when searching for modules.
+    :ref:`module segment option <config-themes-seg-module>`. Paths defined here 
+    have priority when searching for modules.
 
 Extension-specific configuration
 --------------------------------
@@ -236,7 +236,8 @@ Themes
     using :ref:`local themes <config-ext-local_themes>` values of these keys are 
     first searched in the segment description, then in ``segment_data`` key of 
     a local theme, then in ``segment_data`` key of a :ref:`default theme 
-    <config-ext-theme>`.
+    <config-ext-theme>`. For the :ref:`default theme <config-ext-theme>` itself 
+    step 2 is obviously avoided.
 
 ``segments``
     A dict with a ``left`` and a ``right`` list, consisting of segment 
@@ -354,3 +355,74 @@ A segment function must return one of the following values:
 * A list of dicts consisting of a ``contents`` string, and 
   a ``highlight_group`` list. This is useful for providing a particular 
   highlighting group depending on the segment contents.
+
+Local configuration
+===================
+
+Depending on the application used it is possible to override configuration. Here 
+is the list:
+
+Vim overrides
+-------------
+
+Vim configuration can be overridden using the following options:
+
+``g:powerline_config_overrides``
+    Dictionary, recursively merged with contents of 
+    :file:`powerline/config.json`.
+
+``g:powerline_theme_overrides__{theme_name}``
+    Dictionary, recursively merged with contents of 
+    :file:`powerline/themes/vim/{theme_name}.json`. Note that this way you can’t 
+    redefine some value (e.g. segment) in list, only the whole list itself: only 
+    dictionaries are merged recursively.
+
+``g:powerline_config_path``
+    Path (must be expanded, ``~`` shortcut is not supported). Points to the 
+    directory which will be searched for configuration. When this option is 
+    present, none of the other locations are searched.
+
+Powerline script overrides
+--------------------------
+
+Powerline script has a number of options controlling powerline behavior. Here 
+``VALUE`` always means “some JSON object”.
+
+``-c KEY.NESTED_KEY=VALUE`` or ``--config=KEY.NESTED_KEY=VALUE``
+    Overrides options from :file:`powerline/config.json`. 
+    ``KEY.KEY2.KEY3=VALUE`` is a shortcut for ``KEY={"KEY2": {"KEY3": VALUE}}``. 
+    Multiple options (i.e. ``-c K1=V1 -c K2=V2``) are allowed, result (in the 
+    example: ``{"K1": V1, "K2": V2}``) is recursively merged with the contents 
+    of the file.
+
+``-t THEME_NAME.KEY.NESTED_KEY=VALUE`` or ``--theme_option=THEME_NAME.KEY.NESTED_KEY=VALUE``
+    Overrides options from :file:`powerline/themes/{ext}/{THEME_NAME}.json`. 
+    ``KEY.NESTED_KEY=VALUE`` is processed like described above, ``{ext}`` is the 
+    first argument to powerline script. May be passed multiple times.
+
+``-p PATH`` or ``--config_path=PATH``
+    Sets directory where configuration should be read from. If present, no 
+    default locations are searched for configuration. No expansions are 
+    performed by powerline script itself, but ``-p ~/.powerline`` will likely be 
+    expanded by the shell to something like ``-p /home/user/.powerline``.
+
+Ipython overrides
+-----------------
+
+Ipython overrides depend on ipython version. Before ipython-0.11 you should pass 
+additional keyword arguments to setup() function. After ipython-0.11 you should 
+use ``c.Powerline.KEY``. Supported ``KEY`` strings or keyword argument names:
+
+``config_overrides``
+    Overrides options from :file:`powerline/config.json`. Should be a dictionary 
+    that will be recursively merged with the contents of the file.
+
+``theme_overrides``
+    Overrides options from :file:`powerline/themes/ipython/*.json`. Should be 
+    a dictionary where keys are theme names and values are dictionaries which 
+    will be recursively merged with the contents of the given theme.
+
+``path``
+    Sets directory where configuration should be read from. If present, no 
+    default locations are searched for configuration. No expansions are 
+    performed thus you cannot use paths starting with ``~/``.
diff --git a/powerline/__init__.py b/powerline/__init__.py
index e69de29b..5275a53c 100644
--- a/powerline/__init__.py
+++ b/powerline/__init__.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import importlib
+import json
+import os
+import sys
+
+from powerline.colorscheme import Colorscheme
+from powerline.lib import underscore_to_camelcase
+
+
+def load_json_config(search_paths, config_file):
+	config_file += '.json'
+	for path in search_paths:
+		config_file_path = os.path.join(path, config_file)
+		if os.path.isfile(config_file_path):
+			with open(config_file_path, 'r') as config_file_fp:
+				return json.load(config_file_fp)
+	raise IOError('Config file not found in search path: {0}'.format(config_file))
+
+
+class Powerline(object):
+	def __init__(self, ext, renderer_module=None):
+		self.config_paths = self.get_config_paths()
+
+		# Load main config file
+		config = self.load_main_config()
+		common_config = config['common']
+		ext_config = config['ext'][ext]
+		self.ext = ext
+
+		# Load and initialize colorscheme
+		colorscheme_config = self.load_colorscheme_config(ext_config['colorscheme'])
+		colorscheme = Colorscheme(colorscheme_config)
+
+		# Load and initialize extension theme
+		theme_config = self.load_theme_config(ext_config.get('theme', 'default'))
+		common_config['paths'] = [os.path.expanduser(path) for path in common_config.get('paths', [])]
+		self.import_paths = common_config['paths']
+		theme_kwargs = {
+			'ext': ext,
+			'colorscheme': colorscheme,
+			'common_config': common_config,
+			'segment_info': self.get_segment_info(),
+			}
+		local_themes = self.get_local_themes(ext_config.get('local_themes', {}))
+
+		# Load and initialize extension renderer
+		renderer_module_name = renderer_module or ext
+		renderer_module_import = 'powerline.renderers.{0}'.format(renderer_module_name)
+		renderer_class_name = '{0}Renderer'.format(underscore_to_camelcase(renderer_module_name))
+		try:
+			Renderer = getattr(importlib.import_module(renderer_module_import), renderer_class_name)
+		except ImportError as e:
+			sys.stderr.write('Error while importing renderer module: {0}\n'.format(e))
+			sys.exit(1)
+		options = {'term_truecolor': common_config.get('term_24bit_colors', False)}
+		self.renderer = Renderer(theme_config, local_themes, theme_kwargs, **options)
+
+	def get_config_paths(self):
+		config_home = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
+		config_path = os.path.join(config_home, 'powerline')
+		plugin_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'config_files')
+		return [config_path, plugin_path]
+
+	def load_theme_config(self, name):
+		return load_json_config(self.config_paths, os.path.join('themes', self.ext, name))
+
+	def load_main_config(self):
+		return load_json_config(self.config_paths, 'config')
+
+	def load_colorscheme_config(self, name):
+		return load_json_config(self.config_paths, os.path.join('colorschemes', self.ext, name))
+
+	@staticmethod
+	def get_local_themes(local_themes):
+		return {}
+
+	@staticmethod
+	def get_segment_info():
+		return None
diff --git a/powerline/bindings/ipython/post_0_11.py b/powerline/bindings/ipython/post_0_11.py
index 439793d9..89c0ba69 100644
--- a/powerline/bindings/ipython/post_0_11.py
+++ b/powerline/bindings/ipython/post_0_11.py
@@ -1,4 +1,4 @@
-from powerline.core import Powerline
+from powerline.ipython import IpythonPowerline
 
 from IPython.core.prompts import PromptManager
 
@@ -19,8 +19,23 @@ class PowerlinePromptManager(PromptManager):
 		return res if color else res_nocolor
 
 
+class ConfigurableIpythonPowerline(IpythonPowerline):
+	def __init__(self, ip):
+		config = ip.config.Powerline
+		self.config_overrides = config.get('config_overrides')
+		self.theme_overrides = config.get('theme_overrides', {})
+		self.path = config.get('path')
+		super(ConfigurableIpythonPowerline, self).__init__()
+
+
 def load_ipython_extension(ip):
-	powerline = Powerline('ipython')
+	global old_prompt_manager
+
+	old_prompt_manager = ip.prompt_manager
+	powerline = ConfigurableIpythonPowerline(ip)
 
 	ip.prompt_manager = PowerlinePromptManager(powerline=powerline,
 		shell=ip.prompt_manager.shell, config=ip.prompt_manager.config)
+
+def unload_ipython_extension(ip):
+	ip.prompt_manager = old_prompt_manager
diff --git a/powerline/bindings/ipython/pre_0_11.py b/powerline/bindings/ipython/pre_0_11.py
index 18fa8384..0943025a 100644
--- a/powerline/bindings/ipython/pre_0_11.py
+++ b/powerline/bindings/ipython/pre_0_11.py
@@ -1,4 +1,4 @@
-from powerline.core import Powerline
+from powerline.ipython import IpythonPowerline
 from IPython.Prompts import BasePrompt
 from IPython.ipapi import get as get_ipython
 
@@ -18,10 +18,18 @@ class PowerlinePrompt(BasePrompt):
 		return '%s>%s' % ('-' * self.prompt_text_len, ' ' * self.nrspaces)
 
 
-def setup(prompt='1'):
+class ConfigurableIpythonPowerline(IpythonPowerline):
+	def __init__(self, config_overrides=None, theme_overrides={}, path=None):
+		self.config_overrides = config_overrides
+		self.theme_overrides = theme_overrides
+		self.path = path
+		super(ConfigurableIpythonPowerline, self).__init__()
+
+
+def setup(prompt='1', **kwargs):
 	ip = get_ipython()
 
-	powerline = Powerline('ipython')
+	powerline = ConfigurableIpythonPowerline(**kwargs)
 
 	attr = 'prompt' + prompt
 
diff --git a/powerline/bindings/qtile/widget.py b/powerline/bindings/qtile/widget.py
index c9ca5751..dc4132aa 100644
--- a/powerline/bindings/qtile/widget.py
+++ b/powerline/bindings/qtile/widget.py
@@ -3,7 +3,7 @@
 from libqtile import bar
 from libqtile.widget import base
 
-from powerline.core import Powerline as PowerlineCore
+from powerline import Powerline as PowerlineCore
 
 
 class Powerline(base._TextBox):
diff --git a/powerline/bindings/vim/plugin/powerline.vim b/powerline/bindings/vim/plugin/powerline.vim
index a538c9fc..947db8b4 100644
--- a/powerline/bindings/vim/plugin/powerline.vim
+++ b/powerline/bindings/vim/plugin/powerline.vim
@@ -18,15 +18,16 @@ endif
 let s:powerline_pycmd = substitute(get(g:, 'powerline_pycmd', 'py'), '\v^(py)%[thon](3?)$', '\1\2', '')
 let s:powerline_pyeval = get(g:, 'powerline_pyeval', s:powerline_pycmd.'eval')
 
+let s:import_cmd = 'from powerline.vim import VimPowerline'
 try
-	exec s:powerline_pycmd 'from powerline.core import Powerline'
+	exec s:powerline_pycmd s:import_cmd
 catch
 	" An error occured while importing the module, it could be installed
 	" outside of Python's module search paths. Update sys.path and try again.
 	exec s:powerline_pycmd 'import sys, vim'
 	exec s:powerline_pycmd 'sys.path.append(vim.eval(''expand("<sfile>:h:h:h:h:h")''))'
 	try
-		exec s:powerline_pycmd 'from powerline.core import Powerline'
+		exec s:powerline_pycmd s:import_cmd
 	catch
 		call s:CriticalError('An error occured while importing the Powerline package.
 			\ This could be caused by an invalid sys.path setting, or by an incompatible
@@ -35,7 +36,8 @@ catch
 		finish
 	endtry
 endtry
-exec s:powerline_pycmd 'powerline = Powerline("vim", segment_info={})'
+exec s:powerline_pycmd 'powerline = VimPowerline()'
+exec s:powerline_pycmd 'del VimPowerline'
 
 if !get(g:, 'powerline_debugging_pyeval') && exists('*'. s:powerline_pyeval)
 	let s:pyeval = function(s:powerline_pyeval)
diff --git a/powerline/bindings/zsh/__init__.py b/powerline/bindings/zsh/__init__.py
index 75b36658..b0e3ffe4 100644
--- a/powerline/bindings/zsh/__init__.py
+++ b/powerline/bindings/zsh/__init__.py
@@ -1,8 +1,18 @@
 import zsh
-from powerline.core import Powerline
+from powerline.shell import ShellPowerline
+
+
+def get_var_config(var):
+	try:
+		return dict(((k, json.loads(v)) for k, v in zsh.getvalue(var).items()))
+	except:
+		return None
 
 
 class Args(object):
+	ext = ['shell']
+	renderer_module = 'zsh_prompt'
+
 	@property
 	def last_exit_code(self):
 		return zsh.last_exit_code()
@@ -11,6 +21,14 @@ class Args(object):
 	def last_pipe_status(self):
 		return zsh.pipestatus()
 
+	@property
+	def config(self):
+		return get_var_config('POWERLINE_CONFIG')
+
+	@property
+	def theme_option(self):
+		return get_var_config('POWERLINE_THEME_CONFIG')
+
 
 class Prompt(object):
 	__slots__ = ('render', 'side', 'savedpsvar', 'savedps')
@@ -38,6 +56,6 @@ def set_prompt(powerline, psvar, side):
 
 
 def setup():
-	powerline = Powerline(ext='shell', renderer_module='zsh_prompt', segment_info=Args())
+	powerline = ShellPowerline(Args())
 	set_prompt(powerline, 'PS1', 'left')
 	set_prompt(powerline, 'RPS1', 'right')
diff --git a/powerline/config_files/themes/shell/default.json b/powerline/config_files/themes/shell/default.json
index 29946684..a2fb4c53 100644
--- a/powerline/config_files/themes/shell/default.json
+++ b/powerline/config_files/themes/shell/default.json
@@ -1,20 +1,29 @@
 {
 	"default_module": "powerline.segments.common",
+	"segment_data": {
+		"hostname": {
+			"before": " ",
+			"args": {
+				"only_if_ssh": true
+			}
+		},
+		"virtualenv": {
+			"before": "ⓔ  "
+		},
+		"branch": {
+			"before": " "
+		}
+	},
 	"segments": {
 		"left": [
 			{
-				"name": "hostname",
-				"before": " ",
-				"args": {
-					"only_if_ssh": true
-				}
+				"name": "hostname"
 			},
 			{
 				"name": "user"
 			},
 			{
-				"name": "virtualenv",
-				"before": "ⓔ  "
+				"name": "virtualenv"
 			},
 			{
 				"name": "cwd",
@@ -31,8 +40,7 @@
 				"highlight_group": "exit_fail"
 			},
 			{
-				"name": "branch",
-				"before": " "
+				"name": "branch"
 			}
 		]
 	}
diff --git a/powerline/config_files/themes/shell/default_leftonly.json b/powerline/config_files/themes/shell/default_leftonly.json
index a81c9658..46e8c392 100644
--- a/powerline/config_files/themes/shell/default_leftonly.json
+++ b/powerline/config_files/themes/shell/default_leftonly.json
@@ -1,21 +1,32 @@
 {
 	"default_module": "powerline.segments.common",
+	"segment_data": {
+		"hostname": {
+			"before": " ",
+			"args": {
+				"only_if_ssh": true
+			}
+		},
+		"virtualenv": {
+			"before": "ⓔ  "
+		},
+		"branch": {
+			"before": " "
+		}
+	},
 	"segments": {
 		"left": [
 			{
-				"name": "hostname",
-				"before": " "
+				"name": "hostname"
 			},
 			{
 				"name": "user"
 			},
 			{
-				"name": "virtualenv",
-				"before": "ⓔ  "
+				"name": "virtualenv"
 			},
 			{
-				"name": "branch",
-				"before": " "
+				"name": "branch"
 			},
 			{
 				"name": "cwd",
diff --git a/powerline/config_files/themes/tmux/default.json b/powerline/config_files/themes/tmux/default.json
index 2c1ad292..841a75f7 100644
--- a/powerline/config_files/themes/tmux/default.json
+++ b/powerline/config_files/themes/tmux/default.json
@@ -1,16 +1,32 @@
 {
 	"default_module": "powerline.segments.common",
+	"segment_data": {
+		"uptime": {
+			"before": "⇑  "
+		},
+		"external_ip": {
+			"before": "ⓦ  "
+		},
+		"date": {
+			"before": "⌚ "
+		},
+		"email_imap_alert": {
+			"before": "✉ ",
+			"args": {
+				"username": "",
+				"password": ""
+			}
+		}
+	},
 	"segments": {
 		"right": [
 			{
 				"name": "uptime",
-				"before": "⇑  ",
 				"priority": 50,
 				"divider_highlight_group": "background:divider"
 			},
 			{
 				"name": "external_ip",
-				"before": "ⓦ  ",
 				"priority": 50,
 				"divider_highlight_group": "background:divider"
 			},
@@ -35,18 +51,12 @@
 			{
 				"name": "date",
 				"args": {"format": "%H:%M"},
-				"before": "⌚ ",
 				"highlight_group": ["time", "date"],
 				"divider_highlight_group": "time:divider"
 			},
 			{
 				"name": "email_imap_alert",
-				"before": "✉ ",
 				"priority": 10,
-				"args": {
-					"username": "",
-					"password": ""
-				}
 			},
 			{
 				"name": "hostname"
diff --git a/powerline/core.py b/powerline/core.py
deleted file mode 100644
index 6f4d5fe7..00000000
--- a/powerline/core.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import importlib
-import json
-import os
-import sys
-
-from powerline.colorscheme import Colorscheme
-from powerline.matcher import Matcher
-from powerline.lib import underscore_to_camelcase
-
-
-class Powerline(object):
-	def __init__(self, ext, renderer_module=None, segment_info=None, renderer_options={}):
-		config_home = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
-		config_path = os.path.join(config_home, 'powerline')
-		plugin_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'config_files')
-		self.search_paths = [config_path, plugin_path]
-		sys.path[:0] = self.search_paths
-
-		# Load main config file
-		config = self._load_json_config('config')
-		self.config = config['common']
-		self.config_ext = config['ext'][ext]
-
-		# Load and initialize colorscheme
-		colorscheme_config = self._load_json_config(os.path.join('colorschemes', ext, self.config_ext['colorscheme']))
-		colorscheme = Colorscheme(colorscheme_config)
-
-		# Load and initialize extension theme
-		theme_config = self._load_theme_config(ext, self.config_ext.get('theme', 'default'))
-		self.config['paths'] = [os.path.expanduser(path) for path in self.config.get('paths', [])]
-		self.get_matcher = Matcher(ext, self.config['paths']).get
-		theme_kwargs = {
-			'ext': ext,
-			'colorscheme': colorscheme,
-			'common_config': self.config,
-			'segment_info': segment_info,
-			}
-		local_themes = {}
-		for key, local_theme_name in self.config_ext.get('local_themes', {}).items():
-			key = self.get_matcher(key)
-			local_themes[key] = {'config': self._load_theme_config(ext, local_theme_name)}
-
-		# Load and initialize extension renderer
-		renderer_module_name = renderer_module or ext
-		renderer_module_import = 'powerline.renderers.{0}'.format(renderer_module_name)
-		renderer_class_name = '{0}Renderer'.format(underscore_to_camelcase(renderer_module_name))
-		try:
-			Renderer = getattr(importlib.import_module(renderer_module_import), renderer_class_name)
-		except ImportError as e:
-			sys.stderr.write('Error while importing renderer module: {0}\n'.format(e))
-			sys.exit(1)
-		options = {'term_truecolor': self.config.get('term_24bit_colors', False)}
-		options.update(renderer_options)
-		self.renderer = Renderer(theme_config, local_themes, theme_kwargs, **options)
-
-	def add_local_theme(self, key, config):
-		'''Add local themes at runtime (e.g. during vim session).
-
-		Accepts key as first argument (same as keys in config.json:
-		ext/*/local_themes) and configuration dictionary as the second (has
-		format identical to themes/*/*.json)
-
-		Returns True if theme was added successfully and False if theme with
-		the same matcher already exists
-		'''
-		key = self.get_matcher(key)
-		try:
-			self.renderer.add_local_theme(key, {'config': config})
-		except KeyError:
-			return False
-		else:
-			return True
-
-	def _load_theme_config(self, ext, name):
-		return self._load_json_config(os.path.join('themes', ext, name))
-
-	def _load_json_config(self, config_file):
-		config_file += '.json'
-		for path in self.search_paths:
-			config_file_path = os.path.join(path, config_file)
-			if os.path.isfile(config_file_path):
-				with open(config_file_path, 'r') as config_file_fp:
-					return json.load(config_file_fp)
-		raise IOError('Config file not found in search path: {0}'.format(config_file))
diff --git a/powerline/ipython.py b/powerline/ipython.py
new file mode 100644
index 00000000..498ca42b
--- /dev/null
+++ b/powerline/ipython.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from powerline import Powerline
+from powerline.lib import mergedicts
+
+
+class IpythonPowerline(Powerline):
+	def __init__(self):
+		super(IpythonPowerline, self).__init__('ipython')
+
+	def get_config_paths(self):
+		if self.path:
+			return [self.path]
+		else:
+			return super(IpythonPowerline, self).get_config_paths()
+
+	def load_main_config(self):
+		r = super(IpythonPowerline, self).load_main_config()
+		if self.config_overrides:
+			mergedicts(r, self.config_overrides)
+		return r
+
+	def load_theme_config(self, name):
+		r = super(IpythonPowerline, self).load_theme_config(name)
+		if name in self.theme_overrides:
+			mergedicts(r, self.theme_overrides[name])
+		return r
diff --git a/powerline/lib/__init__.py b/powerline/lib/__init__.py
index 5877f65b..ed4eaafb 100644
--- a/powerline/lib/__init__.py
+++ b/powerline/lib/__init__.py
@@ -6,3 +6,12 @@ from powerline.lib.url import urllib_read, urllib_urlencode  # NOQA
 def underscore_to_camelcase(string):
 	'''Return a underscore_separated_string as CamelCase.'''
 	return ''.join(word.capitalize() or '_' for word in string.split('_'))
+
+def mergedicts(d1, d2):
+	'''Recursively merge two dictionaries. First dictionary is modified in-place.
+	'''
+	for k in d2:
+		if k in d1 and type(d1[k]) is dict and type(d2[k]) is dict:
+			mergedicts(d1[k], d2[k])
+		else:
+			d1[k] = d2[k]
diff --git a/powerline/matcher.py b/powerline/matcher.py
index e61b5615..31a0969c 100644
--- a/powerline/matcher.py
+++ b/powerline/matcher.py
@@ -4,19 +4,16 @@ from importlib import import_module
 import sys
 
 
-class Matcher(object):
-	def __init__(self, ext, path):
-		self.ext = ext
-		self.path = path
-
-	def get(self, match_name):
+def gen_matcher_getter(ext, import_paths):
+	def get(match_name):
 		match_module, separator, match_function = match_name.rpartition('.')
 		if not separator:
-			match_module = 'powerline.matchers.{0}'.format(self.ext)
+			match_module = 'powerline.matchers.{0}'.format(ext)
 			match_function = match_name
 		oldpath = sys.path
-		sys.path = self.path + sys.path
+		sys.path = import_paths + sys.path
 		try:
 			return getattr(import_module(match_module), match_function)
 		finally:
 			sys.path = oldpath
+	return get
diff --git a/powerline/shell.py b/powerline/shell.py
new file mode 100644
index 00000000..7e11ac99
--- /dev/null
+++ b/powerline/shell.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+from powerline import Powerline
+from powerline.lib import mergedicts
+
+
+def mergeargs(argvalue):
+	if not argvalue:
+		return None
+	l = argvalue
+	r = dict([argvalue[0]])
+	for subval in argvalue[1:]:
+		mergedicts(r, dict([subval]))
+	return r
+
+
+class ShellPowerline(Powerline):
+	def __init__(self, args):
+		self.args = args
+		self.theme_option = mergeargs(args.theme_option) or {}
+		super(ShellPowerline, self).__init__(args.ext[0], args.renderer_module)
+
+	def get_segment_info(self):
+		return self.args
+
+	def load_main_config(self):
+		r = super(ShellPowerline, self).load_main_config()
+		if self.args.config:
+			mergedicts(r, mergeargs(self.args.config))
+		return r
+
+	def load_theme_config(self, name):
+		r = super(ShellPowerline, self).load_theme_config(name)
+		if name in self.theme_option:
+			mergedicts(r, self.theme_option[name])
+		return r
+
+	def get_config_paths(self):
+		if self.args.config_path:
+			return [self.args.config_path]
+		else:
+			return super(ShellPowerline, self).get_config_paths()
diff --git a/powerline/vim.py b/powerline/vim.py
new file mode 100644
index 00000000..89d3254a
--- /dev/null
+++ b/powerline/vim.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import
+
+from powerline.bindings.vim import vim_get_func
+from powerline import Powerline
+from powerline.lib import mergedicts
+from powerline.matcher import gen_matcher_getter
+import vim
+
+
+vim_exists = vim_get_func('exists', rettype=int)
+
+def _override_from(config, override_varname):
+	if vim_exists(override_varname):
+		# FIXME vim.eval has problem with numeric types, vim.bindeval may be 
+		# absent (and requires converting values to python built-in types), 
+		# vim.eval with typed call like the one I implemented in frawor is slow. 
+		# Maybe eval(vime.eval('string({0})'.format(override_varname)))?
+		overrides = vim.eval(override_varname)
+		mergedicts(config, overrides)
+	return config
+
+
+class VimPowerline(Powerline):
+	def __init__(self):
+		super(VimPowerline, self).__init__('vim')
+
+	def add_local_theme(self, key, config):
+		'''Add local themes at runtime (during vim session).
+
+		Accepts key as first argument (same as keys in config.json:
+		ext/*/local_themes) and configuration dictionary as the second (has
+		format identical to themes/*/*.json)
+
+		Returns True if theme was added successfully and False if theme with
+		the same matcher already exists
+		'''
+		key = self.get_matcher(key)
+		try:
+			self.renderer.add_local_theme(key, {'config': config})
+		except KeyError:
+			return False
+		else:
+			return True
+
+	def load_main_config(self):
+		return _override_from(super(VimPowerline, self).load_main_config(), 'g:powerline_config_overrides')
+
+	def load_theme_config(self, name):
+		# Note: themes with non-[a-zA-Z0-9_] names are impossible to override 
+		# (though as far as I know exists() won’t throw). Won’t fix, use proper 
+		# theme names.
+		return _override_from(super(VimPowerline, self).load_theme_config(name), 'g:powerline_theme_overrides__'+name)
+
+	def get_local_themes(self, local_themes):
+		self.get_matcher = gen_matcher_getter(self.ext, self.import_paths)
+		r = {}
+		for key, local_theme_name in local_themes.items():
+			key = self.get_matcher(key)
+			r[key] = {'config': self.load_theme_config(local_theme_name)}
+		return r
+
+	def get_config_paths(self):
+		if vim_exists('g:powerline_config_path'):
+			return [vim.eval('g:powerline_config_path')]
+		else:
+			return super(VimPowerline, self).get_config_paths()
+
+	@staticmethod
+	def get_segment_info():
+		return {}
diff --git a/scripts/powerline b/scripts/powerline
index acdcf577..2e75825f 100755
--- a/scripts/powerline
+++ b/scripts/powerline
@@ -6,11 +6,11 @@ import sys
 import json
 
 try:
-	from powerline.core import Powerline
+	from powerline.shell import ShellPowerline
 except ImportError:
 	import os
 	sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-	from powerline.core import Powerline  # NOQA
+	from powerline.shell import ShellPowerline  # NOQA
 
 def oval(s):
 	if '=' not in s:
@@ -22,6 +22,20 @@ def oval(s):
 	val = json.loads(s[idx+1:])
 	return (o, val)
 
+def odotval(s):
+	o, val = oval(s)
+	keys = o.split('.')
+	if len(keys) > 1:
+		r = (keys[0], {})
+		rcur = r[1]
+		for key in keys[1:-1]:
+			rcur[key] = {}
+			rcur = rcur[key]
+		rcur[keys[-1]] = val
+		return r
+	else:
+		return (o, val)
+
 parser = argparse.ArgumentParser(description=__doc__)
 parser.add_argument('ext', nargs=1)
 parser.add_argument('side', nargs='?', choices=('left', 'right'))
@@ -29,11 +43,13 @@ parser.add_argument('-r', '--renderer_module', metavar='MODULE', type=str)
 parser.add_argument('-w', '--width', type=int)
 parser.add_argument('--last_exit_code', metavar='INT', type=int)
 parser.add_argument('--last_pipe_status', metavar='LIST', default='', type=lambda s: [int(status) for status in s.split()])
-parser.add_argument('-o', '--renderer_option', nargs='*', metavar='OPTION=VALUE', type=oval)
+parser.add_argument('-c', '--config', metavar='KEY.KEY=VALUE', type=odotval, action='append')
+parser.add_argument('-t', '--theme_option', metavar='THEME.KEY.KEY=VALUE', type=odotval, action='append')
+parser.add_argument('-p', '--config_path', metavar='PATH')
 
 if __name__ == '__main__':
 	args = parser.parse_args()
-	powerline = Powerline(ext=args.ext[0], renderer_module=args.renderer_module, segment_info=args, renderer_options=dict(args.renderer_option or {}))
+	powerline = ShellPowerline(args)
 	rendered = powerline.renderer.render(width=args.width, side=args.side)
 	try:
 		sys.stdout.write(rendered)