[core/theme] Add support for icon themes

Allow sub-themes ("iconsets") to be merged into the "main" theme. That
way, effectively, it's possible to define colors and icons in separate
JSON files.

see #23
This commit is contained in:
Tobi-wan Kenobi 2016-12-08 12:09:21 +01:00
parent 394ef61760
commit 562fd85ca2
5 changed files with 71 additions and 15 deletions

View file

@ -1,6 +1,7 @@
"""Theme support""" """Theme support"""
import os import os
import copy
import json import json
import bumblebee.error import bumblebee.error
@ -12,11 +13,13 @@ def theme_path():
class Theme(object): class Theme(object):
"""Represents a collection of icons and colors""" """Represents a collection of icons and colors"""
def __init__(self, name): def __init__(self, name):
theme = self.load(name)
self._init(self.load(name)) self._init(self.load(name))
def _init(self, data): def _init(self, data):
"""Initialize theme from data structure""" """Initialize theme from data structure"""
for iconset in data.get("icons", []):
self._merge(data, self._load_icons(iconset))
self._theme = data
self._defaults = data.get("defaults", {}) self._defaults = data.get("defaults", {})
def prefix(self, widget): def prefix(self, widget):
@ -31,10 +34,14 @@ class Theme(object):
theme = json.loads(data) theme = json.loads(data)
self._init(theme) self._init(theme)
def load(self, name): def _load_icons(self, name):
path = "{}/icons/".format(theme_path())
return self.load(name, path=path)
def load(self, name, path=theme_path()):
"""Load and parse a theme file""" """Load and parse a theme file"""
path = theme_path()
themefile = "{}/{}.json".format(path, name) themefile = "{}/{}.json".format(path, name)
if os.path.isfile(themefile): if os.path.isfile(themefile):
try: try:
with open(themefile) as data: with open(themefile) as data:
@ -45,9 +52,31 @@ class Theme(object):
raise bumblebee.error.ThemeLoadError("no such theme: {}".format(name)) raise bumblebee.error.ThemeLoadError("no such theme: {}".format(name))
def _get(self, widget, name, default=None): def _get(self, widget, name, default=None):
value = default
value = self._defaults.get(name, value) module_theme = self._theme.get(widget.module(), {})
value = self._defaults.get(name, default)
value = module_theme.get(name, value)
return value return value
# algorithm copied from
# http://blog.impressiver.com/post/31434674390/deep-merge-multiple-python-dicts
# nicely done :)
def _merge(self, target, *args):
if len(args) > 1:
for item in args:
self._merge(item)
return target
item = args[0]
if not isinstance(item, dict):
return item
for key, value in item.items():
if key in target and isinstance(target[key], dict):
self._merge(target[key], value)
else:
target[key] = copy.deepcopy(value)
return target
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -9,8 +9,15 @@ class TestTheme(unittest.TestCase):
def setUp(self): def setUp(self):
self.nonexistentThemeName = "no-such-theme" self.nonexistentThemeName = "no-such-theme"
self.invalidThemeName = "invalid" self.invalidThemeName = "invalid"
self.validThemeName = "solarized-powerline" self.validThemeName = "test"
self.someWidget = MockWidget("foo") self.someWidget = MockWidget("foo")
self.theme = Theme(self.validThemeName)
self.widgetTheme = "test-widget"
self.defaultPrefix = "default-prefix"
self.defaultSuffix = "default-suffix"
self.widgetPrefix = "widget-prefix"
self.widgetSuffix = "widget-suffix"
def test_load_valid_theme(self): def test_load_valid_theme(self):
try: try:
@ -26,14 +33,14 @@ class TestTheme(unittest.TestCase):
with self.assertRaises(ThemeLoadError): with self.assertRaises(ThemeLoadError):
Theme(self.invalidThemeName) Theme(self.invalidThemeName)
def test_prefix(self): def test_default_prefix(self):
theme = Theme(self.validThemeName) self.assertEquals(self.theme.prefix(self.someWidget), self.defaultPrefix)
theme.loads('{"defaults": { "prefix": "test" }}')
self.assertEquals(theme.prefix(self.someWidget), "test")
def test_suffix(self): def test_default_suffix(self):
theme = Theme(self.validThemeName) self.assertEquals(self.theme.suffix(self.someWidget), self.defaultSuffix)
theme.loads('{"defaults": { "suffix": "test" }}')
self.assertEquals(theme.suffix(self.someWidget), "test") def test_widget_prefix(self):
self.someWidget.set_module(self.widgetTheme)
self.assertEquals(self.theme.prefix(self.someWidget), self.widgetPrefix)
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -22,10 +22,17 @@ class MockOutput(object):
class MockWidget(object): class MockWidget(object):
def __init__(self, text): def __init__(self, text):
self._text = text self._text = text
self._module = None
def set_module(self, name):
self._module = name
def update(self, widgets): def update(self, widgets):
pass pass
def module(self):
return self._module
def full_text(self): def full_text(self):
return self._text return self._text

6
themes/icons/test.json Normal file
View file

@ -0,0 +1,6 @@
{
"test-widget": {
"prefix": "widget-prefix",
"suffix": "widget-suffix"
}
}

7
themes/test.json Normal file
View file

@ -0,0 +1,7 @@
{
"icons": [ "test" ],
"defaults": {
"prefix": "default-prefix",
"suffix": "default-suffix"
}
}