Repository: getnikola/nikola

Issue:
install_theme installs unusable themes
If you install a theme that requires a not-installed theme, then it will not work.

For example, if you install the `bootstrap3-jinja` theme it will not work because it needs `bootstrap-jinja` which in turn needs `base-jinja`

Maybe `install_theme` needs to install the whole chain of themes at once?



Discussion:
Yes, installing the chain at once seems to be the proper way to do it... but we have to make it smart enough to reconized the installed chain after the first installation, so when the user install a new theme powered by the installed chain, it will not install all the chain again, just the child theme...


Resolving patch:
diff --git a/nikola/plugins/command/install_theme.py b/nikola/plugins/command/install_theme.py
--- a/nikola/plugins/command/install_theme.py
+++ b/nikola/plugins/command/install_theme.py
@@ -91,26 +91,35 @@ def _execute(self, options, args):
                 print(theme)
             return True
         else:
-            if name in data:
-                utils.makedirs(self.output_dir)
-                utils.LOGGER.notice('Downloading: ' + data[name])
-                zip_file = BytesIO()
-                zip_file.write(requests.get(data[name]).content)
-                utils.LOGGER.notice('Extracting: {0} into themes'.format(name))
-                utils.extract_all(zip_file)
-            else:
-                try:
-                    theme_path = utils.get_theme_path(name)
-                except:
-                    utils.LOGGER.error("Can't find theme " + name)
-                    return False
+            self.do_install(name, data)
+        # See if the theme's parent is available. If not, install it
+        parent_name = utils.get_parent_theme_name(name)
+        try:
+            utils.get_theme_path(parent_name)
+        except:  # Not available
+            self.do_install(parent_name, data)
 
-                utils.makedirs(self.output_dir)
-                dest_path = os.path.join(self.output_dir, name)
-                if os.path.exists(dest_path):
-                    utils.LOGGER.error("{0} is already installed".format(name))
-                    return False
+    def do_install(self, name, data):
+        if name in data:
+            utils.makedirs(self.output_dir)
+            utils.LOGGER.notice('Downloading: ' + data[name])
+            zip_file = BytesIO()
+            zip_file.write(requests.get(data[name]).content)
+            utils.LOGGER.notice('Extracting: {0} into themes'.format(name))
+            utils.extract_all(zip_file)
+        else:
+            try:
+                theme_path = utils.get_theme_path(name)
+            except:
+                utils.LOGGER.error("Can't find theme " + name)
+                return False
+
+            utils.makedirs(self.output_dir)
+            dest_path = os.path.join(self.output_dir, name)
+            if os.path.exists(dest_path):
+                utils.LOGGER.error("{0} is already installed".format(name))
+                return False
 
-                utils.LOGGER.notice('Copying {0} into themes'.format(theme_path))
-                shutil.copytree(theme_path, dest_path)
-                return True
+            utils.LOGGER.notice('Copying {0} into themes'.format(theme_path))
+            shutil.copytree(theme_path, dest_path)
+            return True
diff --git a/nikola/utils.py b/nikola/utils.py
--- a/nikola/utils.py
+++ b/nikola/utils.py
@@ -78,7 +78,7 @@ class ApplicationWarning(Exception):
            'to_datetime', 'apply_filters', 'config_changed', 'get_crumbs',
            'get_tzname', 'get_asset_path', '_reload', 'unicode_str', 'bytes_str',
            'unichr', 'Functionary', 'LocaleBorg', 'sys_encode', 'sys_decode',
-           'makedirs']
+           'makedirs', 'get_parent_theme_name']
 
 
 ENCODING = sys.getfilesystemencoding() or sys.stdin.encoding
@@ -193,19 +193,20 @@ def get_template_engine(themes):
     return 'mako'
 
 
+def get_parent_theme_name(theme_name):
+    parent_path = os.path.join(get_theme_path(theme_name), 'parent')
+    if os.path.isfile(parent_path):
+        with open(parent_path) as fd:
+            return fd.readlines()[0].strip()
+    return None
+
+
 def get_theme_chain(theme):
     """Create the full theme inheritance chain."""
     themes = [theme]
 
-    def get_parent(theme_name):
-        parent_path = os.path.join(get_theme_path(theme_name), 'parent')
-        if os.path.isfile(parent_path):
-            with open(parent_path) as fd:
-                return fd.readlines()[0].strip()
-        return None
-
     while True:
-        parent = get_parent(themes[-1])
+        parent = get_parent_theme_name(themes[-1])
         # Avoid silly loops
         if parent is None or parent in themes:
             break


Test patch: