File: __init__.py

package info (click to toggle)
powerline 2.7-2
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 2,828 kB
  • sloc: python: 22,536; sh: 1,789; ansic: 131; makefile: 74; csh: 51
file content (991 lines) | stat: -rw-r--r-- 31,800 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)

import os
import sys
import logging

from threading import Lock, Event

from powerline.colorscheme import Colorscheme
from powerline.lib.config import ConfigLoader
from powerline.lib.unicode import unicode, safe_unicode, FailedUnicode
from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR
from powerline.lib.dict import mergedicts
from powerline.lib.encoding import get_preferred_output_encoding
from powerline.lib.path import join


class NotInterceptedError(BaseException):
	pass


def _config_loader_condition(path):
	if path and os.path.isfile(path):
		return path
	return None


def _find_config_files(search_paths, config_file, config_loader=None, loader_callback=None):
	config_file += '.json'
	found = False
	for path in search_paths:
		config_file_path = join(path, config_file)
		if os.path.isfile(config_file_path):
			yield config_file_path
			found = True
		elif config_loader:
			config_loader.register_missing(_config_loader_condition, loader_callback, config_file_path)
	if not found:
		raise IOError('Config file not found in search paths ({0}): {1}'.format(
			', '.join(search_paths),
			config_file
		))


class PowerlineLogger(object):
	'''Proxy class for logging.Logger instance

	It emits messages in format ``{ext}:{prefix}:{message}`` where

	``{ext}``
		is a used powerline extension (e.g. “vim”, “shell”, “ipython”).
	``{prefix}``
		is a local prefix, usually a segment name.
	``{message}``
		is the original message passed to one of the logging methods.

	Each of the methods (``critical``, ``exception``, ``info``, ``error``, 
	``warn``, ``debug``) expects to receive message in an ``str.format`` format, 
	not in printf-like format.

	Log is saved to the location :ref:`specified by user <config-common-log>`.
	'''

	def __init__(self, use_daemon_threads, logger, ext):
		self.logger = logger
		self.ext = ext
		self.use_daemon_threads = use_daemon_threads
		self.prefix = ''
		self.last_msgs = {}

	def _log(self, attr, msg, *args, **kwargs):
		prefix = kwargs.get('prefix') or self.prefix
		prefix = self.ext + ((':' + prefix) if prefix else '')
		msg = safe_unicode(msg)
		if args or kwargs:
			args = [safe_unicode(s) if isinstance(s, bytes) else s for s in args]
			kwargs = dict((
				(k, safe_unicode(v) if isinstance(v, bytes) else v)
				for k, v in kwargs.items()
			))
			msg = msg.format(*args, **kwargs)
		msg = prefix + ':' + msg
		key = attr + ':' + prefix
		if msg != self.last_msgs.get(key):
			getattr(self.logger, attr)(msg)
			self.last_msgs[key] = msg

	def critical(self, msg, *args, **kwargs):
		self._log('critical', msg, *args, **kwargs)

	def exception(self, msg, *args, **kwargs):
		self._log('exception', msg, *args, **kwargs)

	def info(self, msg, *args, **kwargs):
		self._log('info', msg, *args, **kwargs)

	def error(self, msg, *args, **kwargs):
		self._log('error', msg, *args, **kwargs)

	def warn(self, msg, *args, **kwargs):
		self._log('warning', msg, *args, **kwargs)

	def debug(self, msg, *args, **kwargs):
		self._log('debug', msg, *args, **kwargs)


_fallback_logger = None


def get_fallback_logger(stream=None):
	global _fallback_logger
	if _fallback_logger:
		return _fallback_logger

	log_format = '%(asctime)s:%(levelname)s:%(message)s'
	formatter = logging.Formatter(log_format)

	level = logging.WARNING
	handler = logging.StreamHandler(stream)
	handler.setLevel(level)
	handler.setFormatter(formatter)

	logger = logging.Logger('powerline')
	logger.setLevel(level)
	logger.addHandler(handler)
	_fallback_logger = PowerlineLogger(None, logger, '_fallback_')
	return _fallback_logger


def _generate_change_callback(lock, key, dictionary):
	def on_file_change(path):
		with lock:
			dictionary[key] = True
	return on_file_change


def get_config_paths():
	'''Get configuration paths from environment variables.

	Uses $XDG_CONFIG_HOME and $XDG_CONFIG_DIRS according to the XDG specification.

	:return: list of paths
	'''
	config_home = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
	config_path = join(config_home, 'powerline')
	config_paths = [config_path]
	config_dirs = os.environ.get('XDG_CONFIG_DIRS', DEFAULT_SYSTEM_CONFIG_DIR)
	if config_dirs is not None:
		config_paths[:0] = reversed([join(d, 'powerline') for d in config_dirs.split(':')])
	config_paths.insert(0, '/etc/powerline')
	config_paths.insert(0, '/usr/share/powerline/config_files')
	return config_paths


def generate_config_finder(get_config_paths=get_config_paths):
	'''Generate find_config_files function

	This function will find .json file given its path.

	:param function get_config_paths:
		Function that being called with no arguments will return a list of paths 
		that should be searched for configuration files.

	:return:
		Function that being given configuration file name will return full path 
		to it or raise IOError if it failed to find the file.
	'''
	config_paths = get_config_paths()
	return lambda *args: _find_config_files(config_paths, *args)


def load_config(cfg_path, find_config_files, config_loader, loader_callback=None):
	'''Load configuration file and setup watches

	Watches are only set up if loader_callback is not None.

	:param str cfg_path:
		Path for configuration file that should be loaded.
	:param function find_config_files:
		Function that finds configuration file. Check out the description of 
		the return value of ``generate_config_finder`` function.
	:param ConfigLoader config_loader:
		Configuration file loader class instance.
	:param function loader_callback:
		Function that will be called by config_loader when change to 
		configuration file is detected.

	:return: Configuration file contents.
	'''
	found_files = find_config_files(cfg_path, config_loader, loader_callback)
	ret = None
	for path in found_files:
		if loader_callback:
			config_loader.register(loader_callback, path)
		if ret is None:
			ret = config_loader.load(path)
		else:
			mergedicts(ret, config_loader.load(path))
	return ret


def _set_log_handlers(common_config, logger, get_module_attr, stream=None):
	'''Set log handlers

	:param dict common_config:
		Configuration dictionary used to create handler.
	:param logging.Logger logger:
		Logger to which handlers will be attached.
	:param func get_module_attr:
		:py:func:`gen_module_attr_getter` output.
	:param file stream:
		Stream to use by default for :py:class:`logging.StreamHandler` in place 
		of :py:attr:`sys.stderr`. May be ``None``.
	'''
	log_targets = common_config['log_file']
	num_handlers = 0
	for log_target in log_targets:
		if log_target is None:
			log_target = ['logging.StreamHandler', []]
		elif isinstance(log_target, unicode):
			log_target = os.path.expanduser(log_target)
			log_dir = os.path.dirname(log_target)
			if log_dir and not os.path.isdir(log_dir):
				os.mkdir(log_dir)
			log_target = ['logging.FileHandler', [[log_target]]]
		module, handler_class_name = log_target[0].rpartition('.')[::2]
		module = module or 'logging.handlers'
		try:
			handler_class_args = log_target[1][0]
		except IndexError:
			if module == 'logging' and handler_class_name == 'StreamHandler':
				handler_class_args = [stream]
			else:
				handler_class_args = ()
		try:
			handler_class_kwargs = log_target[1][1]
		except IndexError:
			handler_class_kwargs = {}
		module = str(module)
		handler_class_name = str(handler_class_name)
		handler_class = get_module_attr(module, handler_class_name)
		if not handler_class:
			continue
		handler = handler_class(*handler_class_args, **handler_class_kwargs)
		try:
			handler_level_name = log_target[2]
		except IndexError:
			handler_level_name = common_config['log_level']
		try:
			handler_format = log_target[3]
		except IndexError:
			handler_format = common_config['log_format']
		handler.setLevel(getattr(logging, handler_level_name))
		handler.setFormatter(logging.Formatter(handler_format))
		logger.addHandler(handler)
		num_handlers += 1
	if num_handlers == 0 and log_targets:
		raise ValueError('Failed to set up any handlers')


def create_logger(common_config, use_daemon_threads=True, ext='__unknown__',
                  import_paths=None, imported_modules=None, stream=None):
	'''Create logger according to provided configuration

	:param dict common_config:
		Common configuration, from :py:func:`finish_common_config`.
	:param bool use_daemon_threads:
		Whether daemon threads should be used. Argument to 
		:py:class:`PowerlineLogger` constructor.
	:param str ext:
		Used extension. Argument to :py:class:`PowerlineLogger` constructor.
	:param set imported_modules:
		Set where imported modules are saved. Argument to 
		:py:func:`gen_module_attr_getter`. May be ``None``, in this case new 
		empty set is used.
	:param file stream:
		Stream to use by default for :py:class:`logging.StreamHandler` in place 
		of :py:attr:`sys.stderr`. May be ``None``.

	:return: Three objects:

		#. :py:class:`logging.Logger` instance.
		#. :py:class:`PowerlineLogger` instance.
		#. Function, output of :py:func:`gen_module_attr_getter`.
	'''
	logger = logging.Logger('powerline')
	level = getattr(logging, common_config['log_level'])
	logger.setLevel(level)

	pl = PowerlineLogger(use_daemon_threads, logger, ext)
	get_module_attr = gen_module_attr_getter(
		pl, common_config['paths'],
		set() if imported_modules is None else imported_modules)

	_set_log_handlers(common_config, logger, get_module_attr, stream)

	return logger, pl, get_module_attr


def get_default_theme(is_unicode=True):
	'''Get default theme used by powerline

	:param bool is_unicode:
		If true, return theme for unicode environments, otherwise return theme 
		that is supposed to be ASCII-only.

	:return: theme name.
	'''
	return 'powerline_terminus' if is_unicode else 'ascii'


def finish_common_config(encoding, common_config):
	'''Add default values to common config and expand ~ in paths

	:param dict common_config:
		Common configuration, as it was just loaded.

	:return:
		Copy of common configuration with all configuration keys and expanded 
		paths.
	'''
	encoding = encoding.lower()
	default_top_theme = get_default_theme(
		encoding.startswith('utf') or encoding.startswith('ucs'))

	common_config = common_config.copy()
	common_config.setdefault('default_top_theme', default_top_theme)
	common_config.setdefault('paths', [])
	common_config.setdefault('watcher', 'auto')
	common_config.setdefault('log_level', 'WARNING')
	common_config.setdefault('log_format', '%(asctime)s:%(levelname)s:%(message)s')
	common_config.setdefault('term_truecolor', False)
	common_config.setdefault('term_escape_style', 'auto')
	common_config.setdefault('ambiwidth', 1)
	common_config.setdefault('additional_escapes', None)
	common_config.setdefault('reload_config', True)
	common_config.setdefault('interval', None)
	common_config.setdefault('log_file', [None])

	if not isinstance(common_config['log_file'], list):
		common_config['log_file'] = [common_config['log_file']]

	common_config['paths'] = [
		os.path.expanduser(path) for path in common_config['paths']
	]

	return common_config


if sys.version_info < (3,):
	# `raise exception[0], None, exception[1]` is a SyntaxError in python-3*
	# Not using ('''…''') because this syntax does not work in python-2.6
	exec((
		'def reraise(exception):\n'
		'	if type(exception) is tuple:\n'
		'		raise exception[0], None, exception[1]\n'
		'	else:\n'
		'		raise exception\n'
	))
else:
	def reraise(exception):
		if type(exception) is tuple:
			raise exception[0].with_traceback(exception[1])
		else:
			raise exception


def gen_module_attr_getter(pl, import_paths, imported_modules):
	def get_module_attr(module, attr, prefix='powerline'):
		'''Import module and get its attribute.

		Replaces ``from {module} import {attr}``.

		:param str module:
			Module name, will be passed as first argument to ``__import__``.
		:param str attr:
			Module attribute, will be passed to ``__import__`` as the only value 
			in ``fromlist`` tuple.

		:return:
			Attribute value or ``None``. Note: there is no way to distinguish 
			between successfull import of attribute equal to ``None`` and 
			unsuccessfull import.
		'''
		oldpath = sys.path
		sys.path = import_paths + sys.path
		module = str(module)
		attr = str(attr)
		try:
			imported_modules.add(module)
			return getattr(__import__(module, fromlist=(attr,)), attr)
		except Exception as e:
			pl.exception('Failed to import attr {0} from module {1}: {2}', attr, module, str(e), prefix=prefix)
			return None
		finally:
			sys.path = oldpath

	return get_module_attr


LOG_KEYS = set(('log_format', 'log_level', 'log_file', 'paths'))
'''List of keys related to logging
'''


def _get_log_keys(common_config):
	'''Return a common configuration copy with only log-related config left

	:param dict common_config:
		Common configuration.

	:return:
		:py:class:`dict` instance which has only keys from 
		:py:attr:`powerline.LOG_KEYS` left.
	'''
	return dict((
		(k, v) for k, v in common_config.items() if k in LOG_KEYS
	))


DEFAULT_UPDATE_INTERVAL = 2
'''Default value for :ref:`update_interval <config-ext-update_interval>`
'''


class Powerline(object):
	'''Main powerline class, entrance point for all powerline uses. Sets 
	powerline up and loads the configuration.

	:param str ext:
		extension used. Determines where configuration files will 
		searched and what renderer module will be used. Affected: used ``ext`` 
		dictionary from :file:`powerline/config.json`, location of themes and 
		colorschemes, render module (``powerline.renders.{ext}``).
	:param str renderer_module:
		Overrides renderer module (defaults to ``ext``). Should be the name of 
		the package imported like this: ``powerline.renderers.{render_module}``. 
		If this parameter contains a dot ``powerline.renderers.`` is not 
		prepended. There is also a special case for renderers defined in 
		toplevel modules: ``foo.`` (note: dot at the end) tries to get renderer 
		from module ``foo`` (because ``foo`` (without dot) tries to get renderer 
		from module ``powerline.renderers.foo``). When ``.foo`` (with leading 
		dot) variant is used ``renderer_module`` will be 
		``powerline.renderers.{ext}{renderer_module}``.
	:param bool run_once:
		Determines whether :py:meth:`render` method will be run only once 
		during python session.
	:param Logger logger:
		If present no new logger will be created and the provided logger will be 
		used.
	:param bool use_daemon_threads:
		When creating threads make them daemon ones.
	:param Event shutdown_event:
		Use this Event as shutdown_event instead of creating new event.
	:param ConfigLoader config_loader:
		Instance of the class that manages (re)loading of the configuration.
	'''

	def __init__(self, *args, **kwargs):
		self.init_args = (args, kwargs)
		self.init(*args, **kwargs)

	def init(self,
	         ext,
	         renderer_module=None,
	         run_once=False,
	         logger=None,
	         use_daemon_threads=True,
	         shutdown_event=None,
	         config_loader=None):
		'''Do actual initialization.

		__init__ function only stores the arguments and runs this function. This 
		function exists for powerline to be able to reload itself: it is easier 
		to make ``__init__`` store arguments and call overriddable ``init`` than 
		tell developers that each time they override Powerline.__init__ in 
		subclasses they must store actual arguments.
		'''
		self.ext = ext
		self.run_once = run_once
		self.logger = logger
		self.had_logger = bool(self.logger)
		self.use_daemon_threads = use_daemon_threads

		if not renderer_module:
			self.renderer_module = 'powerline.renderers.' + ext
		elif '.' not in renderer_module:
			self.renderer_module = 'powerline.renderers.' + renderer_module
		elif renderer_module.startswith('.'):
			self.renderer_module = 'powerline.renderers.' + ext + renderer_module
		elif renderer_module.endswith('.'):
			self.renderer_module = renderer_module[:-1]
		else:
			self.renderer_module = renderer_module

		self.find_config_files = generate_config_finder(self.get_config_paths)

		self.cr_kwargs_lock = Lock()
		self.cr_kwargs = {}
		self.cr_callbacks = {}
		for key in ('main', 'colors', 'colorscheme', 'theme'):
			self.cr_kwargs['load_' + key] = True
			self.cr_callbacks[key] = _generate_change_callback(
				self.cr_kwargs_lock,
				'load_' + key,
				self.cr_kwargs
			)

		self.shutdown_event = shutdown_event or Event()
		self.config_loader = config_loader or ConfigLoader(shutdown_event=self.shutdown_event, run_once=run_once)
		self.run_loader_update = False

		self.renderer_options = {}

		self.prev_common_config = None
		self.prev_ext_config = None
		self.pl = None
		self.setup_args = ()
		self.setup_kwargs = {}
		self.imported_modules = set()
		self.update_interval = DEFAULT_UPDATE_INTERVAL

	get_encoding = staticmethod(get_preferred_output_encoding)
	'''Get encoding used by the current application

	Usually returns encoding of the current locale.
	'''

	def create_logger(self):
		'''Create logger

		This function is used to create logger unless it was already specified 
		at initialization.

		:return: Three objects:

			#. :py:class:`logging.Logger` instance.
			#. :py:class:`PowerlineLogger` instance.
			#. Function, output of :py:func:`gen_module_attr_getter`.
		'''
		return create_logger(
			common_config=self.common_config,
			use_daemon_threads=self.use_daemon_threads,
			ext=self.ext,
			imported_modules=self.imported_modules,
			stream=self.default_log_stream,
		)

	def create_renderer(self, load_main=False, load_colors=False, load_colorscheme=False, load_theme=False):
		'''(Re)create renderer object. Can be used after Powerline object was 
		successfully initialized. If any of the below parameters except 
		``load_main`` is True renderer object will be recreated.

		:param bool load_main:
			Determines whether main configuration file (:file:`config.json`) 
			should be loaded. If appropriate configuration changes implies 
			``load_colorscheme`` and ``load_theme`` and recreation of renderer 
			object. Won’t trigger recreation if only unrelated configuration 
			changed.
		:param bool load_colors:
			Determines whether colors configuration from :file:`colors.json` 
			should be (re)loaded.
		:param bool load_colorscheme:
			Determines whether colorscheme configuration should be (re)loaded.
		:param bool load_theme:
			Determines whether theme configuration should be reloaded.
		'''
		common_config_differs = False
		ext_config_differs = False
		if load_main:
			self._purge_configs('main')
			config = self.load_main_config()
			self.common_config = finish_common_config(self.get_encoding(), config['common'])
			if self.common_config != self.prev_common_config:
				common_config_differs = True

				load_theme = (load_theme
					or not self.prev_common_config
					or self.prev_common_config['default_top_theme'] != self.common_config['default_top_theme'])

				log_keys_differ = (not self.prev_common_config or (
					_get_log_keys(self.prev_common_config) != _get_log_keys(self.common_config)
				))

				self.prev_common_config = self.common_config

				if log_keys_differ:
					if self.had_logger:
						self.pl = PowerlineLogger(self.use_daemon_threads, self.logger, self.ext)
						self.get_module_attr = gen_module_attr_getter(
							self.pl, self.common_config['paths'], self.imported_modules)
					else:
						self.logger, self.pl, self.get_module_attr = self.create_logger()
					self.config_loader.pl = self.pl

				if not self.run_once:
					self.config_loader.set_watcher(self.common_config['watcher'])

				mergedicts(self.renderer_options, dict(
					pl=self.pl,
					term_truecolor=self.common_config['term_truecolor'],
					term_escape_style=self.common_config['term_escape_style'],
					ambiwidth=self.common_config['ambiwidth'],
					tmux_escape=self.common_config['additional_escapes'] == 'tmux',
					screen_escape=self.common_config['additional_escapes'] == 'screen',
					theme_kwargs={
						'ext': self.ext,
						'common_config': self.common_config,
						'run_once': self.run_once,
						'shutdown_event': self.shutdown_event,
						'get_module_attr': self.get_module_attr,
					},
				))

				if not self.run_once and self.common_config['reload_config']:
					interval = self.common_config['interval']
					self.config_loader.set_interval(interval)
					self.run_loader_update = (interval is None)
					if interval is not None and not self.config_loader.is_alive():
						self.config_loader.start()

			self.ext_config = config['ext'][self.ext]

			top_theme = (
				self.ext_config.get('top_theme')
				or self.common_config['default_top_theme']
			)
			self.theme_levels = (
				os.path.join('themes', top_theme),
				os.path.join('themes', self.ext, '__main__'),
			)
			self.renderer_options['theme_kwargs']['top_theme'] = top_theme

			if self.ext_config != self.prev_ext_config:
				ext_config_differs = True
				if (
					not self.prev_ext_config
					or self.ext_config.get('components') != self.prev_ext_config.get('components')
				):
					self.setup_components(self.ext_config.get('components'))
				if (
					not self.prev_ext_config
					or self.ext_config.get('local_themes') != self.prev_ext_config.get('local_themes')
				):
					self.renderer_options['local_themes'] = self.get_local_themes(self.ext_config.get('local_themes'))
				self.update_interval = self.ext_config.get('update_interval', 2)
				load_colorscheme = (
					load_colorscheme
					or not self.prev_ext_config
					or self.prev_ext_config['colorscheme'] != self.ext_config['colorscheme']
				)
				load_theme = (
					load_theme
					or not self.prev_ext_config
					or self.prev_ext_config['theme'] != self.ext_config['theme']
				)
				self.prev_ext_config = self.ext_config

		create_renderer = load_colors or load_colorscheme or load_theme or common_config_differs or ext_config_differs

		if load_colors:
			self._purge_configs('colors')
			self.colors_config = self.load_colors_config()

		if load_colorscheme or load_colors:
			self._purge_configs('colorscheme')
			if load_colorscheme:
				self.colorscheme_config = self.load_colorscheme_config(self.ext_config['colorscheme'])
			self.renderer_options['theme_kwargs']['colorscheme'] = (
				Colorscheme(self.colorscheme_config, self.colors_config))

		if load_theme:
			self._purge_configs('theme')
			self.renderer_options['theme_config'] = self.load_theme_config(self.ext_config.get('theme', 'default'))

		if create_renderer:
			Renderer = self.get_module_attr(self.renderer_module, 'renderer')
			if not Renderer:
				if hasattr(self, 'renderer'):
					return
				else:
					raise ImportError('Failed to obtain renderer')

			# Renderer updates configuration file via segments’ .startup thus it 
			# should be locked to prevent state when configuration was updated, 
			# but .render still uses old renderer.
			try:
				renderer = Renderer(**self.renderer_options)
			except Exception as e:
				self.exception('Failed to construct renderer object: {0}', str(e))
				if not hasattr(self, 'renderer'):
					raise
			else:
				self.renderer = renderer

	default_log_stream = sys.stdout
	'''Default stream for default log handler

	Usually it is ``sys.stderr``, but there is sometimes a reason to prefer 
	``sys.stdout`` or a custom file-like object. It is not supposed to be used 
	to write to some file.
	'''

	def setup_components(self, components):
		'''Run component-specific setup

		:param set components:
			Set of the enabled componets or None.

		Should be overridden by subclasses.
		'''
		pass

	@staticmethod
	def get_config_paths():
		'''Get configuration paths.

		Should be overridden in subclasses in order to provide a way to override 
		used paths.

		:return: list of paths
		'''
		return get_config_paths()

	def load_config(self, cfg_path, cfg_type):
		'''Load configuration and setup watches

		:param str cfg_path:
			Path to the configuration file without any powerline configuration 
			directory or ``.json`` suffix.
		:param str cfg_type:
			Configuration type. May be one of ``main`` (for ``config.json`` 
			file), ``colors``, ``colorscheme``, ``theme``.

		:return: dictionary with loaded configuration.
		'''
		return load_config(
			cfg_path,
			self.find_config_files,
			self.config_loader,
			self.cr_callbacks[cfg_type]
		)

	def _purge_configs(self, cfg_type):
		function = self.cr_callbacks[cfg_type]
		self.config_loader.unregister_functions(set((function,)))
		self.config_loader.unregister_missing(set(((self.find_config_files, function),)))

	def load_main_config(self):
		'''Get top-level configuration.

		:return: dictionary with :ref:`top-level configuration <config-main>`.
		'''
		return self.load_config('config', 'main')

	def _load_hierarhical_config(self, cfg_type, levels, ignore_levels):
		'''Load and merge multiple configuration files

		:param str cfg_type:
			Type of the loaded configuration files (e.g. ``colorscheme``, 
			``theme``).
		:param list levels:
			Configuration names resembling levels in hierarchy, sorted by 
			priority. Configuration file names with higher priority should go 
			last.
		:param set ignore_levels:
			If only files listed in this variable are present then configuration 
			file is considered not loaded: at least one file on the level not 
			listed in this variable must be present.
		'''
		config = {}
		loaded = 0
		exceptions = []
		for i, cfg_path in enumerate(levels):
			try:
				lvl_config = self.load_config(cfg_path, cfg_type)
			except IOError as e:
				if sys.version_info < (3,):
					tb = sys.exc_info()[2]
					exceptions.append((e, tb))
				else:
					exceptions.append(e)
			else:
				if i not in ignore_levels:
					loaded += 1
				mergedicts(config, lvl_config)
		if not loaded:
			for exception in exceptions:
				if type(exception) is tuple:
					e = exception[0]
				else:
					e = exception
				self.exception('Failed to load %s: {0}' % cfg_type, e, exception=exception)
			raise e
		return config

	def load_colorscheme_config(self, name):
		'''Get colorscheme.

		:param str name:
			Name of the colorscheme to load.

		:return: dictionary with :ref:`colorscheme configuration <config-colorschemes>`.
		'''
		levels = (
			os.path.join('colorschemes', name),
			os.path.join('colorschemes', self.ext, '__main__'),
			os.path.join('colorschemes', self.ext, name),
		)
		return self._load_hierarhical_config('colorscheme', levels, (1,))

	def load_theme_config(self, name):
		'''Get theme configuration.

		:param str name:
			Name of the theme to load.

		:return: dictionary with :ref:`theme configuration <config-themes>`
		'''
		levels = self.theme_levels + (
			os.path.join('themes', self.ext, name),
		)
		return self._load_hierarhical_config('theme', levels, (0, 1,))

	def load_colors_config(self):
		'''Get colorscheme.

		:return: dictionary with :ref:`colors configuration <config-colors>`.
		'''
		return self.load_config('colors', 'colors')

	@staticmethod
	def get_local_themes(local_themes):
		'''Get local themes. No-op here, to be overridden in subclasses if 
		required.

		:param dict local_themes:
			Usually accepts ``{matcher_name : theme_name}``. May also receive 
			None in case there is no local_themes configuration.

		:return:
			anything accepted by ``self.renderer.get_theme`` and processable by 
			``self.renderer.add_local_theme``. Renderer module is determined by 
			``__init__`` arguments, refer to its documentation.
		'''
		return None

	def update_renderer(self):
		'''Updates/creates a renderer if needed.'''
		if self.run_loader_update:
			self.config_loader.update()
		cr_kwargs = None
		with self.cr_kwargs_lock:
			if self.cr_kwargs:
				cr_kwargs = self.cr_kwargs.copy()
		if cr_kwargs:
			try:
				self.create_renderer(**cr_kwargs)
			except Exception as e:
				self.exception('Failed to create renderer: {0}', str(e))
				if hasattr(self, 'renderer'):
					with self.cr_kwargs_lock:
						self.cr_kwargs.clear()
				else:
					raise
			else:
				with self.cr_kwargs_lock:
					self.cr_kwargs.clear()

	def render(self, *args, **kwargs):
		'''Update/create renderer if needed and pass all arguments further to 
		``self.renderer.render()``.
		'''
		try:
			self.update_renderer()
			return self.renderer.render(*args, **kwargs)
		except Exception as e:
			exc = e
			try:
				self.exception('Failed to render: {0}', str(e))
			except Exception as e:
				exc = e
			ret = FailedUnicode(safe_unicode(exc))
			if kwargs.get('output_width', False):
				ret = ret, len(ret)
			return ret

	def render_above_lines(self, *args, **kwargs):
		'''Like .render(), but for ``self.renderer.render_above_lines()``
		'''
		try:
			self.update_renderer()
			for line in self.renderer.render_above_lines(*args, **kwargs):
				yield line
		except Exception as e:
			exc = e
			try:
				self.exception('Failed to render: {0}', str(e))
			except Exception as e:
				exc = e
			yield FailedUnicode(safe_unicode(exc))

	def setup(self, *args, **kwargs):
		'''Setup the environment to use powerline.

		Must not be overridden by subclasses. This one only saves setup 
		arguments for :py:meth:`reload` method and calls :py:meth:`do_setup`.
		'''
		self.shutdown_event.clear()
		self.setup_args = args
		self.setup_kwargs.update(kwargs)
		self.do_setup(*args, **kwargs)

	@staticmethod
	def do_setup():
		'''Function that does initialization

		Should be overridden by subclasses. May accept any number of regular or 
		keyword arguments.
		'''
		pass

	def reload(self):
		'''Reload powerline after update.

		Should handle most (but not all) powerline updates.

		Purges out all powerline modules and modules imported by powerline for 
		segment and matcher functions. Requires defining ``setup`` function that 
		updates reference to main powerline object.

		.. warning::
			Not guaranteed to work properly, use it at your own risk. It 
			may break your python code.
		'''
		import sys
		modules = self.imported_modules | set((module for module in sys.modules if module.startswith('powerline')))
		modules_holder = []
		for module in modules:
			try:
				# Needs to hold module to prevent garbage collecting until they 
				# are all reloaded.
				modules_holder.append(sys.modules.pop(module))
			except KeyError:
				pass
		PowerlineClass = getattr(__import__(self.__module__, fromlist=(self.__class__.__name__,)), self.__class__.__name__)
		self.shutdown(set_event=True)
		init_args, init_kwargs = self.init_args
		powerline = PowerlineClass(*init_args, **init_kwargs)
		powerline.setup(*self.setup_args, **self.setup_kwargs)

	def shutdown(self, set_event=True):
		'''Shut down all background threads.

		:param bool set_event:
			Set ``shutdown_event`` and call ``renderer.shutdown`` which should 
			shut down all threads. Set it to False unless you are exiting an 
			application.

			If set to False this does nothing more then resolving reference 
			cycle ``powerline → config_loader → bound methods → powerline`` by 
			unsubscribing from config_loader events.
		'''
		if set_event:
			self.shutdown_event.set()
			try:
				self.renderer.shutdown()
			except AttributeError:
				pass
		functions = tuple(self.cr_callbacks.values())
		self.config_loader.unregister_functions(set(functions))
		self.config_loader.unregister_missing(set(((self.find_config_files, function) for function in functions)))

	def __enter__(self):
		return self

	def __exit__(self, *args):
		self.shutdown()

	def exception(self, msg, *args, **kwargs):
		if 'prefix' not in kwargs:
			kwargs['prefix'] = 'powerline'
		exception = kwargs.pop('exception', None)
		pl = getattr(self, 'pl', None) or get_fallback_logger(self.default_log_stream)
		if exception:
			try:
				reraise(exception)
			except Exception:
				return pl.exception(msg, *args, **kwargs)
		return pl.exception(msg, *args, **kwargs)