def get_data(theme): data = {} data['chain'] = utils.get_theme_chain(theme, DIR) data['name'] = theme readme = utils.get_asset_path('README.md', data['chain'], _themes_dir=DIR) conf_sample = utils.get_asset_path('conf.py.sample', data['chain'], _themes_dir=DIR) if readme: data['readme'] = io.open(readme, 'r', encoding='utf-8').read() else: data['readme'] = 'No README.md file available.' if conf_sample: data['confpy'] = pygments.highlight( io.open(conf_sample, 'r', encoding='utf-8').read(), PythonLexer(), HtmlFormatter(cssclass='code')) else: data['confpy'] = None data['bootswatch'] = ('bootstrap' in data['chain'] or 'bootstrap-jinja' in data['chain'] or 'bootstrap3-jinja' in data['chain'] or 'bootstrap3' in data['chain']) and \ 'bootstrap3-gradients' not in data['chain'] data['engine'] = utils.get_template_engine(data['chain'], DIR) data['chain'] = data['chain'][::-1] data['allver'] = [] for v in ALL_VERSIONS_SUPPORTED: if glob.glob('v{0}/*'.format(v)): data['allver'].append(v) return data
def get_data(theme): data = {} data['chain'] = utils.get_theme_chain(theme) data['name'] = theme readme = utils.get_asset_path('README.md', data['chain']) conf_sample = utils.get_asset_path('conf.py.sample', data['chain']) if readme: data['readme'] = codecs.open(readme, 'r', 'utf8').read() else: data['readme'] = 'No README.md file available.' if conf_sample: data['confpy'] = pygments.highlight( codecs.open(conf_sample, 'r', 'utf8').read(), PythonLexer(), HtmlFormatter(cssclass='code')) else: data['confpy'] = None data['bootswatch'] = ('bootstrap' in data['chain'] or 'bootstrap-jinja' in data['chain'] or 'bootstrap3-jinja' in data['chain'] or 'bootstrap3' in data['chain']) and \ 'bootstrap3-gradients' not in data['chain'] data['engine'] = utils.get_template_engine(data['chain']) data['chain'] = data['chain'][::-1] return data
def build_demo(theme, themes_dir, demo_source, demo_destination): demo_destination = os.path.abspath(demo_destination) if os.path.isdir(demo_source): shutil.rmtree(demo_source) if os.path.isdir(demo_destination): shutil.rmtree(demo_destination) sys.stderr.write('=> Building {}...\n'.format(theme)) subprocess.check_call(["nikola", "init", "-qd", demo_source], stdout=subprocess.PIPE) os.symlink(os.path.abspath(themes_dir), os.path.abspath("/".join([demo_source, "themes"]))) conf_path = "/".join([demo_source, "conf.py"]) book_path = "/".join([demo_source, "templates", "book.tmpl"]) lorem_path = "/".join([demo_source, "posts", "lorem-ipsum.rst"]) # Get custom required settings from the theme themes = utils.get_theme_chain(theme, themes_dirs=[themes_dir, 'themes']) engine_path = utils.get_asset_path('engine', themes) extra_conf_path = utils.get_asset_path('conf.py.sample', themes) extra_conf = '' if extra_conf_path: extra_conf = io.open(extra_conf_path, 'r', encoding="utf-8").read() if engine_path: engine = io.open(engine_path, 'r', encoding="utf-8").read().strip() if engine == 'jinja': shutil.copy('book-jinja.tmpl', book_path) with io.open(conf_path, "a", encoding="utf-8") as conf: conf.write(u"\n\nTHEME = '{0}'\nUSE_BUNDLES = False\nOUTPUT_FOLDER = '{1}'\nSOCIAL_BUTTONS_CODE = ''\nUSE_BASE_TAG = False\n\n{2}\n".format(theme, demo_destination, extra_conf)) shutil.copy(LOREM_BASE, lorem_path) with cd(demo_source): subprocess.check_call(["nikola", "build"], stdout=subprocess.PIPE) sys.stderr.write('=> Done building {}\n'.format(theme))
def is_asset_duplicated(path, themes): # First get the path for the asset with whole theme chain p1 = utils.get_asset_path(path, themes) # Get the path for asset with truncated theme chain p2 = utils.get_asset_path(path, themes[1:]) # Compare if p1 and p2: return filecmp.cmp(p1, p2, False), p1, p2 else: return False, p1, p2
def is_asset_duplicated(path, themes): # First get the path for the asset with whole theme chain p1 = utils.get_asset_path(path, themes, _themes_dir="v7") # Get the path for asset with truncated theme chain p2 = utils.get_asset_path(path, themes[1:], _themes_dir="v7") # README.md is ok to duplicate if "README.md" in path: return False, p1, p2 # Compare if p1 and p2: return filecmp.cmp(p1, p2, False), p1, p2 else: return False, p1, p2
def is_asset_duplicated(path, themes): # First get the path for the asset with whole theme chain p1 = utils.get_asset_path(path, themes) # Get the path for asset with truncated theme chain p2 = utils.get_asset_path(path, themes[1:]) # Ignore some files that may be duplicated if any(i in path for i in ('README.md', 'engine', 'AUTHORS.txt', 'conf.py.sample')): return False, p1, p2 # Compare if p1 and p2: return filecmp.cmp(p1, p2, False), p1, p2 else: return False, p1, p2
def new_theme(self, name, engine, parent): """Create a new theme.""" base = 'themes' themedir = os.path.join(base, name) LOGGER.info("Creating theme {0} with parent {1} and engine {2} in {3}".format(name, parent, engine, themedir)) if not os.path.exists(base): os.mkdir(base) LOGGER.info("Created directory {0}".format(base)) # Check if engine and parent match engine_file = utils.get_asset_path('engine', utils.get_theme_chain(parent, self.site.themes_dirs)) with io.open(engine_file, 'r', encoding='utf-8') as fh: parent_engine = fh.read().strip() if parent_engine != engine: LOGGER.error("Cannot use engine {0} because parent theme '{1}' uses {2}".format(engine, parent, parent_engine)) return 2 # Create theme if not os.path.exists(themedir): os.mkdir(themedir) LOGGER.info("Created directory {0}".format(themedir)) else: LOGGER.error("Theme already exists") return 2 with io.open(os.path.join(themedir, 'parent'), 'w', encoding='utf-8') as fh: fh.write(parent + '\n') LOGGER.info("Created file {0}".format(os.path.join(themedir, 'parent'))) with io.open(os.path.join(themedir, 'engine'), 'w', encoding='utf-8') as fh: fh.write(engine + '\n') LOGGER.info("Created file {0}".format(os.path.join(themedir, 'engine'))) LOGGER.info("Theme {0} created successfully.".format(themedir)) LOGGER.notice('Remember to set THEME="{0}" in conf.py to use this theme.'.format(name))
def sanity_check(theme=None): if theme is None: # Check them all for theme in theme_list(): sanity_check(theme) return themes = utils.get_theme_chain(theme, themes_dirs=['v7']) themes_bn = [os.path.basename(i) for i in themes] engine = utils.get_template_engine(themes) # Inheritance checks # All themes must inherit from base if themes_bn[-1] != 'base': error("theme {0} doesn't inherit from base".format(theme)) # All jinja themes must inherit from base-jinja if engine == "jinja" and "base-jinja" not in themes_bn: error("theme {0} is jinja-based and doesn't inherit from base-jinja".format(theme)) # All mako themes must NOT inherit from base-jinja if engine == "mako" and "base-jinja" in themes_bn: error("theme {0} is mako-based and inherits from base-jinja".format(theme)) # Detect exact asset duplication in theme chain for root, dirs, files in os.walk("v7/"+theme): for f in files: path = "/".join([root, f]) asset = path.split("/", 2)[-1] r, p1, p2 = is_asset_duplicated(asset, themes) if r: error("duplicated asset: {0} {1}".format(p1, p2)) # Detect deprecated names and anonymous namespaces for root, dirs, files in os.walk("v7/"+theme+"/templates"): for f in files: path = "/".join([root, f]) with io.open(path, "r", encoding="utf-8") as inf: data = inf.read() for k, exceptions in blacklist: if k in data and f not in exceptions: error("theme '{0}' contains deprecated name '{1}' in {2}".format(theme, k, path)) # Ensure the theme has a README.md if utils.get_asset_path('README.md', themes) is None: error("theme '{0}' has no README.md".format(theme)) # Ensure the theme has a meta file if utils.get_asset_path(theme + '.theme', themes) is None: warning("theme '{0}' has no {0}.theme meta file".format(theme))
def gen_tasks(self): """Generate CSS out of LESS sources.""" kw = { 'cache_folder': self.site.config['CACHE_FOLDER'], 'themes': self.site.THEMES, } # Find where in the theme chain we define the LESS targets # There can be many *.less in the folder, but we only will build # the ones listed in less/targets targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES) try: with codecs.open(targets_path, "rb", "utf-8") as inf: targets = [x.strip() for x in inf.readlines()] except Exception: targets = [] for theme_name in kw['themes']: src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder) for task in utils.copy_tree(src, os.path.join(kw['cache_folder'], self.sources_folder)): task['basename'] = 'prepare_less_sources' yield task # Build targets and write CSS files base_path = utils.get_theme_path(self.site.THEMES[0]) dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets', 'css') # Make everything depend on all sources, rough but enough deps = glob.glob(os.path.join( base_path, self.sources_folder, "*{0}".format(self.sources_ext))) def compile_target(target, dst): utils.makedirs(dst_dir) src = os.path.join(kw['cache_folder'], self.sources_folder, target) try: compiled = subprocess.check_output([self.compiler_name, src]) except OSError: utils.req_missing([self.compiler_name], 'build LESS files (and use this theme)', False, False) with open(dst, "wb+") as outf: outf.write(compiled) yield self.group_task() for target in targets: dst = os.path.join(dst_dir, target.replace(self.sources_ext, ".css")) yield { 'basename': self.name, 'name': dst, 'targets': [dst], 'file_dep': deps, 'task_dep': ['prepare_less_sources'], 'actions': ((compile_target, [target, dst]), ), 'uptodate': [utils.config_changed(kw)], 'clean': True }
def gen_tasks(self): """Bundle assets using WebAssets.""" kw = { 'filters': self.site.config['FILTERS'], 'output_folder': self.site.config['OUTPUT_FOLDER'], 'cache_folder': self.site.config['CACHE_FOLDER'], 'theme_bundles': get_theme_bundles(self.site.THEMES), 'themes': self.site.THEMES, 'files_folders': self.site.config['FILES_FOLDERS'], 'code_color_scheme': self.site.config['CODE_COLOR_SCHEME'], } def build_bundle(output, inputs): out_dir = os.path.join(kw['output_folder'], os.path.dirname(output)) inputs = [i for i in inputs if os.path.isfile( os.path.join(out_dir, i))] cache_dir = os.path.join(kw['cache_folder'], 'webassets') utils.makedirs(cache_dir) env = webassets.Environment(out_dir, os.path.dirname(output), cache=cache_dir) if inputs: bundle = webassets.Bundle(*inputs, output=os.path.basename(output)) env.register(output, bundle) # This generates the file env[output].urls() else: with open(os.path.join(out_dir, os.path.basename(output)), 'wb+'): pass # Create empty file yield self.group_task() if (webassets is not None and self.site.config['USE_BUNDLES'] is not False): for name, _files in kw['theme_bundles'].items(): output_path = os.path.join(kw['output_folder'], name) dname = os.path.dirname(name) files = [] for fname in _files: # paths are relative to dirname files.append(os.path.join(dname, fname)) file_dep = [os.path.join(kw['output_folder'], fname) for fname in files if utils.get_asset_path(fname, self.site.THEMES, self.site.config['FILES_FOLDERS'])] task = { 'file_dep': list(file_dep), 'task_dep': ['copy_assets'], 'basename': str(self.name), 'name': str(output_path), 'actions': [(build_bundle, (name, files))], 'targets': [output_path], 'uptodate': [ utils.config_changed({ 1: kw, 2: file_dep })], 'clean': True, } yield utils.apply_filters(task, kw['filters'])
def gen_tasks(self): """Bundle assets using WebAssets.""" kw = { 'filters': self.site.config['FILTERS'], 'output_folder': self.site.config['OUTPUT_FOLDER'], 'cache_folder': self.site.config['CACHE_FOLDER'], 'theme_bundles': get_theme_bundles(self.site.THEMES), 'themes': self.site.THEMES, 'files_folders': self.site.config['FILES_FOLDERS'], 'code_color_scheme': self.site.config['CODE_COLOR_SCHEME'], } def build_bundle(output, inputs): out_dir = os.path.join(kw['output_folder'], os.path.dirname(output)) inputs = [i for i in inputs if os.path.isfile( os.path.join(out_dir, i))] cache_dir = os.path.join(kw['cache_folder'], 'webassets') if not os.path.isdir(cache_dir): os.makedirs(cache_dir) env = webassets.Environment(out_dir, os.path.dirname(output), cache=cache_dir) bundle = webassets.Bundle(*inputs, output=os.path.basename(output)) env.register(output, bundle) # This generates the file env[output].urls() flag = False if (webassets is not None and self.site.config['USE_BUNDLES'] is not False): for name, files in kw['theme_bundles'].items(): output_path = os.path.join(kw['output_folder'], name) dname = os.path.dirname(name) file_dep = [utils.get_asset_path( os.path.join(dname, fname), kw['themes'], kw['files_folders']) for fname in files ] file_dep = filter(None, file_dep) # removes missing files task = { 'file_dep': file_dep, 'basename': str(self.name), 'name': str(output_path), 'actions': [(build_bundle, (name, files))], 'targets': [output_path], 'uptodate': [utils.config_changed(kw)], 'clean': True, } flag = True yield utils.apply_filters(task, kw['filters']) if flag is False: # No page rendered, yield a dummy task yield { 'basename': self.name, 'uptodate': [True], 'name': 'None', 'actions': [], }
def test_get_asset_path(): assert get_asset_path('assets/css/nikola_rst.css', get_theme_chain('bootstrap4', ['themes'])).replace( '\\', '/').endswith('nikola/data/themes/base/assets/css/nikola_rst.css') assert get_asset_path('assets/css/theme.css', get_theme_chain('bootstrap4', ['themes'])).replace( '\\', '/').endswith( 'nikola/data/themes/bootstrap4/assets/css/theme.css') assert get_asset_path( 'nikola.py', get_theme_chain('bootstrap4', ['themes']), {'nikola': ''}).replace( '\\', '/').endswith('nikola/nikola.py') assert get_asset_path('nikola.py', get_theme_chain( 'bootstrap4', ['themes']), {'nikola': 'nikola'}) is None assert get_asset_path( 'nikola/nikola.py', get_theme_chain('bootstrap4', ['themes']), {'nikola': 'nikola'}).replace( '\\', '/').endswith('nikola/nikola.py')
def gen_tasks(self): """Generate CSS out of LESS sources.""" kw = {"cache_folder": self.site.config["CACHE_FOLDER"], "themes": self.site.THEMES} # Find where in the theme chain we define the LESS targets # There can be many *.less in the folder, but we only will build # the ones listed in less/targets targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES) try: with codecs.open(targets_path, "rb", "utf-8") as inf: targets = [x.strip() for x in inf.readlines()] except Exception: targets = [] for theme_name in kw["themes"]: src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder) for task in utils.copy_tree(src, os.path.join(kw["cache_folder"], self.sources_folder)): # task['basename'] = self.name yield task # Build targets and write CSS files base_path = utils.get_theme_path(self.site.THEMES[0]) dst_dir = os.path.join(self.site.config["OUTPUT_FOLDER"], "assets", "css") # Make everything depend on all sources, rough but enough deps = glob.glob(os.path.join(base_path, self.sources_folder, "*{0}".format(self.sources_ext))) def compile_target(target, dst): if not os.path.isdir(dst_dir): os.makedirs(dst_dir) src = os.path.join(kw["cache_folder"], self.sources_folder, target) compiled = subprocess.check_output([self.compiler_name, src]) with open(dst, "wb+") as outf: outf.write(compiled) for target in targets: dst = os.path.join(dst_dir, target.replace(self.sources_ext, ".css")) yield { "basename": self.name, "name": dst, "targets": [dst], "file_dep": deps, "actions": ((compile_target, [target, dst]),), "uptodate": [utils.config_changed(kw)], "clean": True, } if not targets: yield {"basename": self.name, "actions": []}
def gen_tasks(self): """Bundle assets using WebAssets.""" kw = { "filters": self.site.config["FILTERS"], "output_folder": self.site.config["OUTPUT_FOLDER"], "cache_folder": self.site.config["CACHE_FOLDER"], "theme_bundles": get_theme_bundles(self.site.THEMES), "themes": self.site.THEMES, "files_folders": self.site.config["FILES_FOLDERS"], "code_color_scheme": self.site.config["CODE_COLOR_SCHEME"], } def build_bundle(output, inputs): out_dir = os.path.join(kw["output_folder"], os.path.dirname(output)) inputs = [i for i in inputs if os.path.isfile(os.path.join(out_dir, i))] cache_dir = os.path.join(kw["cache_folder"], "webassets") if not os.path.isdir(cache_dir): os.makedirs(cache_dir) env = webassets.Environment(out_dir, os.path.dirname(output), cache=cache_dir) bundle = webassets.Bundle(*inputs, output=os.path.basename(output)) env.register(output, bundle) # This generates the file env[output].urls() flag = False if webassets is not None and self.site.config["USE_BUNDLES"] is not False: for name, files in kw["theme_bundles"].items(): output_path = os.path.join(kw["output_folder"], name) dname = os.path.dirname(name) file_dep = [ utils.get_asset_path(os.path.join(dname, fname), kw["themes"], kw["files_folders"]) for fname in files ] file_dep = filter(None, file_dep) # removes missing files task = { "file_dep": file_dep, "basename": str(self.name), "name": str(output_path), "actions": [(build_bundle, (name, files))], "targets": [output_path], "uptodate": [utils.config_changed(kw)], } flag = True yield utils.apply_filters(task, kw["filters"]) if flag is False: # No page rendered, yield a dummy task yield {"basename": self.name, "uptodate": [True], "name": "None", "actions": []}
def sanity_check(theme=None): if theme is None: # Check them all for theme in theme_list(): sanity_check(theme) return themes = utils.get_theme_chain(theme, themes_dirs=['v7']) themes_bn = [os.path.basename(i) for i in themes] engine = utils.get_template_engine(themes) # Inheritance checks # All themes must inherit from base if themes_bn[-1] != 'base': error("theme {0} doesn't inherit from base".format(theme)) # All jinja themes must inherit from base-jinja if engine == "jinja" and "base-jinja" not in themes_bn: error("theme {0} is jinja-based and doesn't inherit from base-jinja". format(theme)) # All mako themes must NOT inherit from base-jinja if engine == "mako" and "base-jinja" in themes_bn: error("theme {0} is mako-based and inherits from base-jinja".format( theme)) # Detect exact asset duplication in theme chain for root, dirs, files in os.walk("v7/" + theme): for f in files: path = "/".join([root, f]) asset = path.split("/", 2)[-1] r, p1, p2 = is_asset_duplicated(asset, themes) if r: error("duplicated asset: {0} {1}".format(p1, p2)) # Detect deprecated names and anonymous namespaces for root, dirs, files in os.walk("v7/" + theme + "/templates"): for f in files: path = "/".join([root, f]) with io.open(path, "r", encoding="utf-8") as inf: data = inf.read() for k, exceptions in blacklist: if k in data and f not in exceptions: error("theme '{0}' contains deprecated name '{1}' in {2}". format(theme, k, path)) # Ensure the theme has a README.md if utils.get_asset_path('README.md', themes) is None: error("theme '{0}' has no README.md".format(theme))
def new_theme(self, name, engine, parent): """Create a new theme.""" base = 'themes' themedir = os.path.join(base, name) LOGGER.info( "Creating theme {0} with parent {1} and engine {2} in {3}".format( name, parent, engine, themedir)) if not os.path.exists(base): os.mkdir(base) LOGGER.info("Created directory {0}".format(base)) # Check if engine and parent match engine_file = utils.get_asset_path( 'engine', utils.get_theme_chain(parent, self.site.themes_dirs)) with io.open(engine_file, 'r', encoding='utf-8') as fh: parent_engine = fh.read().strip() if parent_engine != engine: LOGGER.error( "Cannot use engine {0} because parent theme '{1}' uses {2}". format(engine, parent, parent_engine)) return 2 # Create theme if not os.path.exists(themedir): os.mkdir(themedir) LOGGER.info("Created directory {0}".format(themedir)) else: LOGGER.error("Theme already exists") return 2 with io.open(os.path.join(themedir, 'parent'), 'w', encoding='utf-8') as fh: fh.write(parent + '\n') LOGGER.info("Created file {0}".format( os.path.join(themedir, 'parent'))) with io.open(os.path.join(themedir, 'engine'), 'w', encoding='utf-8') as fh: fh.write(engine + '\n') LOGGER.info("Created file {0}".format( os.path.join(themedir, 'engine'))) LOGGER.info("Theme {0} created successfully.".format(themedir)) LOGGER.notice( 'Remember to set THEME="{0}" in conf.py to use this theme.'.format( name))
def gen_tasks(self): """Generate a robots.txt file.""" kw = { "base_url": self.site.config["BASE_URL"], "site_url": self.site.config["SITE_URL"], "output_folder": self.site.config["OUTPUT_FOLDER"], "files_folders": self.site.config["FILES_FOLDERS"], "robots_exclusions": self.site.config["ROBOTS_EXCLUSIONS"], "filters": self.site.config["FILTERS"], } sitemapindex_url = urljoin(kw["base_url"], "sitemapindex.xml") robots_path = os.path.join(kw["output_folder"], "robots.txt") def write_robots(): if kw["site_url"] != urljoin(kw["site_url"], "/"): utils.LOGGER.warn("robots.txt not ending up in server root, will be useless") with io.open(robots_path, "w+", encoding="utf8") as outf: outf.write("Sitemap: {0}\n\n".format(sitemapindex_url)) if kw["robots_exclusions"]: outf.write("User-Agent: *\n") for loc in kw["robots_exclusions"]: outf.write("Disallow: {0}\n".format(loc)) yield self.group_task() if not utils.get_asset_path("robots.txt", [], files_folders=kw["files_folders"], output_dir=False): yield utils.apply_filters( { "basename": self.name, "name": robots_path, "targets": [robots_path], "actions": [(write_robots)], "uptodate": [utils.config_changed(kw, "nikola.plugins.task.robots")], "clean": True, "task_dep": ["sitemap"], }, kw["filters"], ) elif kw["robots_exclusions"]: utils.LOGGER.warn( "Did not generate robots.txt as one already exists in FILES_FOLDERS. ROBOTS_EXCLUSIONS will not have any affect on the copied file." ) else: utils.LOGGER.debug("Did not generate robots.txt as one already exists in FILES_FOLDERS.")
def gen_tasks(self): """Generate a robots.txt.""" kw = { "base_url": self.site.config["BASE_URL"], "site_url": self.site.config["SITE_URL"], "output_folder": self.site.config["OUTPUT_FOLDER"], "files_folders": self.site.config['FILES_FOLDERS'], "robots_exclusions": self.site.config["ROBOTS_EXCLUSIONS"] } if kw["site_url"] != urljoin(kw["site_url"], "/"): utils.LOGGER.warn( 'robots.txt not ending up in server root, will be useless') sitemapindex_url = urljoin(kw["base_url"], "sitemapindex.xml") robots_path = os.path.join(kw['output_folder'], "robots.txt") def write_robots(): with codecs.open(robots_path, 'wb+', 'utf8') as outf: outf.write("Sitemap: {0}\n\n".format(sitemapindex_url)) if kw["robots_exclusions"]: outf.write("User-Agent: *\n") for loc in kw["robots_exclusions"]: outf.write("Disallow: {0}\n".format(loc)) yield self.group_task() if not utils.get_asset_path("robots.txt", [], files_folders=kw["files_folders"]): yield { "basename": self.name, "name": robots_path, "targets": [robots_path], "actions": [(write_robots)], "uptodate": [utils.config_changed(kw)], "clean": True, "task_dep": ["sitemap"] } elif kw["robots_exclusions"]: utils.LOGGER.warn( 'Did not generate robots.txt as one already exists in FILES_FOLDERS. ROBOTS_EXCLUSIONS will not have any affect on the copied fie.' ) else: utils.LOGGER.debug( 'Did not generate robots.txt as one already exists in FILES_FOLDERS.' )
def get_data(theme): data = {} data['name'] = theme readme = utils.get_asset_path('README', [theme]) if readme: data['readme'] = open(readme).read() else: data['readme'] = 'No readme file available' data['chain'] = utils.get_theme_chain(theme) data['bootswatch'] = ('bootstrap' in data['chain'] or 'bootstrap-jinja' in data['chain'] or 'bootstrap3-jinja' in data['chain'] or 'bootstrap3' in data['chain']) and \ 'bootstrap3-gradients' not in data['chain'] data['engine'] = utils.get_template_engine(data['chain']) data['chain'] = data['chain'][::-1] return data
def init_theme(theme): t_path = "/".join(["sites", theme]) o_path = os.path.abspath("/".join(["output", "v6", theme])) if os.path.isdir(t_path): shutil.rmtree(t_path) if os.path.isdir(o_path): shutil.rmtree(o_path) subprocess.check_call(["nikola", "init", "--demo", t_path], stdout=subprocess.PIPE) os.symlink(os.path.abspath("themes"), os.path.abspath("/".join([t_path, "themes"]))) conf_path = "/".join([t_path,"conf.py"]) # Get custom required settings from the theme themes = utils.get_theme_chain(theme) extra_conf_path = utils.get_asset_path('conf.py.sample', themes) extra_conf = '' if extra_conf_path: extra_conf = open(extra_conf_path, 'r').read() with codecs.open(conf_path, "a", "utf-8") as conf: conf.write("\n\n{2}\n\nTHEME = '{0}'\n\nOUTPUT_FOLDER = '{1}'\n\nSOCIAL_BUTTONS_CODE = ''\n".format(theme, o_path, extra_conf))
def gen_tasks(self): """Generate a robots.txt.""" kw = { "base_url": self.site.config["BASE_URL"], "site_url": self.site.config["SITE_URL"], "output_folder": self.site.config["OUTPUT_FOLDER"], "files_folders": self.site.config['FILES_FOLDERS'], "robots_exclusions": self.site.config["ROBOTS_EXCLUSIONS"] } sitemapindex_url = urljoin(kw["base_url"], "sitemapindex.xml") robots_path = os.path.join(kw['output_folder'], "robots.txt") def write_robots(): if kw["site_url"] != urljoin(kw["site_url"], "/"): utils.LOGGER.warn('robots.txt not ending up in server root, will be useless') with codecs.open(robots_path, 'wb+', 'utf8') as outf: outf.write("Sitemap: {0}\n\n".format(sitemapindex_url)) if kw["robots_exclusions"]: outf.write("User-Agent: *\n") for loc in kw["robots_exclusions"]: outf.write("Disallow: {0}\n".format(loc)) yield self.group_task() if not utils.get_asset_path("robots.txt", [], files_folders=kw["files_folders"]): yield { "basename": self.name, "name": robots_path, "targets": [robots_path], "actions": [(write_robots)], "uptodate": [utils.config_changed(kw)], "clean": True, "task_dep": ["sitemap"] } elif kw["robots_exclusions"]: utils.LOGGER.warn('Did not generate robots.txt as one already exists in FILES_FOLDERS. ROBOTS_EXCLUSIONS will not have any affect on the copied fie.') else: utils.LOGGER.debug('Did not generate robots.txt as one already exists in FILES_FOLDERS.')
def init_theme(theme): t_path = "/".join(["sites", theme]) o_path = os.path.abspath("/".join(["output", "v7", theme])) if os.path.isdir(t_path): shutil.rmtree(t_path) if os.path.isdir(o_path): shutil.rmtree(o_path) subprocess.check_call(["nikola", "init", "-qd", t_path], stdout=subprocess.PIPE) os.symlink(os.path.abspath("v7"), os.path.abspath("/".join([t_path, "themes"]))) conf_path = "/".join([t_path, "conf.py"]) # Get custom required settings from the theme themes = utils.get_theme_chain(theme, _themes_dir="v7") extra_conf_path = utils.get_asset_path("conf.py.sample", themes, _themes_dir="v7") extra_conf = "" if extra_conf_path: extra_conf = io.open(extra_conf_path, "r", encoding="utf-8").read() with io.open(conf_path, "a", encoding="utf-8") as conf: conf.write( "\n\n{2}\n\nTHEME = '{0}'\n\nUSE_BUNDLES = False\n\nOUTPUT_FOLDER = '{1}'\n\nSOCIAL_BUTTONS_CODE = ''\nUSE_BASE_TAG = False\n".format( theme, o_path, extra_conf ) )
def init_theme(theme): t_path = "/".join(["sites", theme]) o_path = os.path.abspath("/".join(["output", "v7", theme])) if os.path.isdir(t_path): shutil.rmtree(t_path) if os.path.isdir(o_path): shutil.rmtree(o_path) subprocess.check_call(["nikola", "init", "-qd", t_path], stdout=subprocess.PIPE) os.symlink(os.path.abspath("v7"), os.path.abspath("/".join([t_path, "themes"]))) conf_path = "/".join([t_path, "conf.py"]) # Get custom required settings from the theme themes = utils.get_theme_chain(theme, themes_dirs=['v7', 'themes']) extra_conf_path = utils.get_asset_path('conf.py.sample', themes) extra_conf = '' if extra_conf_path: extra_conf = io.open(extra_conf_path, 'r', encoding="utf-8").read() with io.open(conf_path, "a", encoding="utf-8") as conf: conf.write( u"\n\n{2}\n\nTHEME = '{0}'\n\nUSE_BUNDLES = False\n\nOUTPUT_FOLDER = '{1}'\n\nSOCIAL_BUTTONS_CODE = ''\nUSE_BASE_TAG = False\n" .format(theme, o_path, extra_conf))
def gen_tasks(self): """Generate CSS out of LESS sources.""" self.compiler_name = self.site.config['LESS_COMPILER'] self.compiler_options = self.site.config['LESS_OPTIONS'] kw = { 'cache_folder': self.site.config['CACHE_FOLDER'], 'themes': self.site.THEMES, } tasks = {} # Find where in the theme chain we define the LESS targets # There can be many *.less in the folder, but we only will build # the ones listed in less/targets if os.path.isfile(os.path.join(self.sources_folder, "targets")): targets_path = os.path.join(self.sources_folder, "targets") else: targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES) try: with codecs.open(targets_path, "rb", "utf-8") as inf: targets = [x.strip() for x in inf.readlines()] except Exception: targets = [] for task in utils.copy_tree(self.sources_folder, os.path.join(kw['cache_folder'], self.sources_folder)): if task['name'] in tasks: continue task['basename'] = 'prepare_less_sources' tasks[task['name']] = task yield task for theme_name in kw['themes']: src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder) for task in utils.copy_tree(src, os.path.join(kw['cache_folder'], self.sources_folder)): task['basename'] = 'prepare_less_sources' yield task # Build targets and write CSS files base_path = utils.get_theme_path(self.site.THEMES[0]) dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets', 'css') # Make everything depend on all sources, rough but enough deps = [] if os.path.isfile(os.path.join(self.sources_folder, "targets")): deps += glob.glob(os.path.join(kw['cache_folder'], self.sources_folder, '*{0}'.format(self.sources_ext))) else: deps += glob.glob(os.path.join(base_path, self.sources_folder, '*{0}'.format(self.sources_ext))) def compile_target(target, dst): utils.makedirs(dst_dir) src = os.path.join(kw['cache_folder'], self.sources_folder, target) run_in_shell = sys.platform == 'win32' try: compiled = subprocess.check_output([self.compiler_name] + self.compiler_options + [src], shell=run_in_shell) except OSError: utils.req_missing([self.compiler_name], 'build LESS files (and use this theme)', False, False) with open(dst, "wb+") as outf: outf.write(compiled) yield self.group_task() for target in targets: dst = os.path.join(dst_dir, target.replace(self.sources_ext, ".css")) yield { 'basename': self.name, 'name': dst, 'targets': [dst], 'file_dep': deps, 'task_dep': ['prepare_less_sources'], 'actions': ((compile_target, [target, dst]), ), 'uptodate': [utils.config_changed(kw)], 'clean': True }
def gen_tasks(self): """Bundle assets.""" kw = { 'filters': self.site.config['FILTERS'], 'output_folder': self.site.config['OUTPUT_FOLDER'], 'cache_folder': self.site.config['CACHE_FOLDER'], 'theme_bundles': get_theme_bundles(self.site.THEMES), 'themes': self.site.THEMES, 'files_folders': self.site.config['FILES_FOLDERS'], 'code_color_scheme': self.site.config['CODE_COLOR_SCHEME'], } def build_bundle(output, inputs): out_dir = os.path.join(kw['output_folder'], os.path.dirname(output)) inputs = [ os.path.join(out_dir, os.path.relpath(i, out_dir)) for i in inputs if os.path.isfile(i) ] with open(os.path.join(out_dir, os.path.basename(output)), 'wb+') as out_fh: for i in inputs: with open(i, 'rb') as in_fh: shutil.copyfileobj(in_fh, out_fh) out_fh.write(b'\n') yield self.group_task() if self.site.config['USE_BUNDLES']: for name, _files in kw['theme_bundles'].items(): output_path = os.path.join(kw['output_folder'], name) dname = os.path.dirname(name) files = [] for fname in _files: # paths are relative to dirname files.append(os.path.join(dname, fname)) file_dep = [ os.path.join(kw['output_folder'], fname) for fname in files if utils.get_asset_path(fname, self.site.THEMES, self.site.config['FILES_FOLDERS'], output_dir=kw['output_folder']) or fname == os.path.join('assets', 'css', 'code.css') ] # code.css will be generated by us if it does not exist in # FILES_FOLDERS or theme assets. It is guaranteed that the # generation will happen before this task. task = { 'file_dep': list(file_dep), 'task_dep': ['copy_assets', 'copy_files'], 'basename': str(self.name), 'name': str(output_path), 'actions': [(build_bundle, (name, file_dep))], 'targets': [output_path], 'uptodate': [ utils.config_changed({ 1: kw, 2: file_dep }, 'nikola.plugins.task.bundles') ], 'clean': True, } yield utils.apply_filters(task, kw['filters'])
def gen_tasks(self): """Generate CSS out of Sass sources.""" self.logger = utils.get_logger('build_sass', self.site.loghandlers) self.compiler_name = self.site.config['SASS_COMPILER'] self.compiler_options = self.site.config['SASS_OPTIONS'] kw = { 'cache_folder': self.site.config['CACHE_FOLDER'], 'themes': self.site.THEMES, } tasks = {} # Find where in the theme chain we define the Sass targets # There can be many *.sass/*.scss in the folder, but we only # will build the ones listed in sass/targets if os.path.isfile(os.path.join(self.sources_folder, "targets")): targets_path = os.path.join(self.sources_folder, "targets") else: targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES) try: with codecs.open(targets_path, "rb", "utf-8") as inf: targets = [x.strip() for x in inf.readlines()] except Exception: targets = [] for task in utils.copy_tree(self.sources_folder, os.path.join(kw['cache_folder'], self.sources_folder)): if task['name'] in tasks: continue task['basename'] = 'prepare_sass_sources' tasks[task['name']] = task yield task for theme_name in kw['themes']: src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder) for task in utils.copy_tree(src, os.path.join(kw['cache_folder'], self.sources_folder)): if task['name'] in tasks: continue task['basename'] = 'prepare_sass_sources' tasks[task['name']] = task yield task # Build targets and write CSS files base_path = utils.get_theme_path(self.site.THEMES[0]) dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets', 'css') # Make everything depend on all sources, rough but enough deps = [] for ext in self.sources_ext: if os.path.isfile(os.path.join(self.sources_folder, "targets")): deps += glob.glob(os.path.join(kw['cache_folder'], self.sources_folder, '*{0}'.format(ext))) else: deps += glob.glob(os.path.join(base_path, self.sources_folder, '*{0}'.format(ext))) def compile_target(target, dst): utils.makedirs(dst_dir) run_in_shell = sys.platform == 'win32' src = os.path.join(kw['cache_folder'], self.sources_folder, target) try: compiled = subprocess.check_output([self.compiler_name] + self.compiler_options + [src], shell=run_in_shell) except OSError: utils.req_missing([self.compiler_name], 'build Sass files (and use this theme)', False, False) with open(dst, "wb+") as outf: outf.write(compiled) yield self.group_task() # We can have file conflicts. This is a way to prevent them. # I orignally wanted to use sets and their cannot-have-duplicates # magic, but I decided not to do this so we can show the user # what files were problematic. # If we didn’t do this, there would be a cryptic message from doit # instead. seennames = {} for target in targets: base = os.path.splitext(target)[0] dst = os.path.join(dst_dir, base + ".css") if base in seennames: self.logger.error( 'Duplicate filenames for Sass compiled files: {0} and ' '{1} (both compile to {2})'.format( seennames[base], target, base + ".css")) else: seennames.update({base: target}) yield { 'basename': self.name, 'name': dst, 'targets': [dst], 'file_dep': deps, 'task_dep': ['prepare_sass_sources'], 'actions': ((compile_target, [target, dst]), ), 'uptodate': [utils.config_changed(kw)], 'clean': True }
def gen_tasks(self): """Create tasks to copy the assets of the whole theme chain. If a file is present on two themes, use the version from the "youngest" theme. """ kw = { "themes": self.site.THEMES, "files_folders": self.site.config['FILES_FOLDERS'], "output_folder": self.site.config['OUTPUT_FOLDER'], "filters": self.site.config['FILTERS'], "code_color_scheme": self.site.config['CODE_COLOR_SCHEME'], "code.css_selectors": 'pre.code', "code.css_head": '/* code.css file generated by Nikola */\n', "code.css_close": "\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n", } tasks = {} code_css_path = os.path.join(kw['output_folder'], 'assets', 'css', 'code.css') code_css_input = utils.get_asset_path('assets/css/code.css', themes=kw['themes'], files_folders=kw['files_folders'], output_dir=None) yield self.group_task() for theme_name in kw['themes']: src = os.path.join(utils.get_theme_path(theme_name), 'assets') dst = os.path.join(kw['output_folder'], 'assets') for task in utils.copy_tree(src, dst): if task['name'] in tasks: continue tasks[task['name']] = task task['uptodate'] = [utils.config_changed(kw, 'nikola.plugins.task.copy_assets')] task['basename'] = self.name if code_css_input: if 'file_dep' not in task: task['file_dep'] = [] task['file_dep'].append(code_css_input) yield utils.apply_filters(task, kw['filters']) # Check whether or not there is a code.css file around. if not code_css_input and kw['code_color_scheme']: def create_code_css(): from pygments.formatters import get_formatter_by_name formatter = get_formatter_by_name('html', style=kw["code_color_scheme"]) utils.makedirs(os.path.dirname(code_css_path)) with io.open(code_css_path, 'w+', encoding='utf8') as outf: outf.write(kw["code.css_head"]) outf.write(formatter.get_style_defs(kw["code.css_selectors"])) outf.write(kw["code.css_close"]) if os.path.exists(code_css_path): with io.open(code_css_path, 'r', encoding='utf-8') as fh: testcontents = fh.read(len(kw["code.css_head"])) == kw["code.css_head"] else: testcontents = False task = { 'basename': self.name, 'name': code_css_path, 'targets': [code_css_path], 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.copy_assets'), testcontents], 'actions': [(create_code_css, [])], 'clean': True, } yield utils.apply_filters(task, kw['filters'])
def _execute(self, options, args): """Given a swatch name and a parent theme, creates a custom theme.""" name = options['name'] swatch = options['swatch'] if not swatch: LOGGER.error('The -s option is mandatory') return 1 parent = options['parent'] version = '4' # Check which Bootstrap version to use themes = utils.get_theme_chain(parent, self.site.themes_dirs) if _check_for_theme('bootstrap', themes) or _check_for_theme('bootstrap-jinja', themes): version = '2' elif _check_for_theme('bootstrap3', themes) or _check_for_theme('bootstrap3-jinja', themes): version = '3' elif _check_for_theme('bootstrap4', themes) or _check_for_theme('bootstrap4-jinja', themes): version = '4' elif not _check_for_theme('bootstrap4', themes) and not _check_for_theme('bootstrap4-jinja', themes): LOGGER.warning( '"subtheme" only makes sense for themes that use bootstrap') elif _check_for_theme('bootstrap3-gradients', themes) or _check_for_theme('bootstrap3-gradients-jinja', themes): LOGGER.warning( '"subtheme" doesn\'t work well with the bootstrap3-gradients family') LOGGER.info("Creating '{0}' theme from '{1}' and '{2}'".format( name, swatch, parent)) utils.makedirs(os.path.join('themes', name, 'assets', 'css')) for fname in ('bootstrap.min.css', 'bootstrap.css'): if swatch in [ 'bubblegum', 'business-tycoon', 'charming', 'daydream', 'executive-suite', 'good-news', 'growth', 'harbor', 'hello-world', 'neon-glow', 'pleasant', 'retro', 'vibrant-sea', 'wizardry']: # Hackerthemes LOGGER.info( 'Hackertheme-based subthemes often require you use a custom font for full effect.') if version != '4': LOGGER.error( 'The hackertheme subthemes are only available for Bootstrap 4.') return 1 if fname == 'bootstrap.css': url = 'https://raw.githubusercontent.com/HackerThemes/theme-machine/master/dist/{swatch}/css/bootstrap4-{swatch}.css'.format( swatch=swatch) else: url = 'https://raw.githubusercontent.com/HackerThemes/theme-machine/master/dist/{swatch}/css/bootstrap4-{swatch}.min.css'.format( swatch=swatch) else: # Bootswatch url = 'https://bootswatch.com' if version: url += '/' + version url = '/'.join((url, swatch, fname)) LOGGER.info("Downloading: " + url) r = requests.get(url) if r.status_code > 299: LOGGER.error('Error {} getting {}', r.status_code, url) return 1 data = r.text with open(os.path.join('themes', name, 'assets', 'css', fname), 'w+') as output: output.write(data) with open(os.path.join('themes', name, '%s.theme' % name), 'w+') as output: parent_theme_data_path = utils.get_asset_path( '%s.theme' % parent, themes) cp = configparser.ConfigParser() cp.read(parent_theme_data_path) cp['Theme']['parent'] = parent cp['Family'] = {'family': cp['Family']['family']} cp.write(output) LOGGER.info( 'Theme created. Change the THEME setting to "{0}" to use it.'.format(name))
def gen_tasks(self): """Generate CSS out of Sass sources.""" self.logger = utils.get_logger('build_sass', self.site.loghandlers) kw = { 'cache_folder': self.site.config['CACHE_FOLDER'], 'themes': self.site.THEMES, } # Find where in the theme chain we define the Sass targets # There can be many *.sass/*.scss in the folder, but we only # will build the ones listed in sass/targets targets_path = utils.get_asset_path( os.path.join(self.sources_folder, "targets"), self.site.THEMES) try: with codecs.open(targets_path, "rb", "utf-8") as inf: targets = [x.strip() for x in inf.readlines()] except Exception: targets = [] for theme_name in kw['themes']: src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder) for task in utils.copy_tree( src, os.path.join(kw['cache_folder'], self.sources_folder)): task['basename'] = 'prepare_sass_sources' yield task # Build targets and write CSS files base_path = utils.get_theme_path(self.site.THEMES[0]) dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets', 'css') # Make everything depend on all sources, rough but enough deps = glob.glob( os.path.join(base_path, self.sources_folder, *("*{0}".format(ext) for ext in self.sources_ext))) def compile_target(target, dst): utils.makedirs(dst_dir) src = os.path.join(kw['cache_folder'], self.sources_folder, target) compiled = subprocess.check_output([self.compiler_name, src]) with open(dst, "wb+") as outf: outf.write(compiled) yield self.group_task() # We can have file conflicts. This is a way to prevent them. # I orignally wanted to use sets and their cannot-have-duplicates # magic, but I decided not to do this so we can show the user # what files were problematic. # If we didn’t do this, there would be a cryptic message from doit # instead. seennames = {} for target in targets: base = os.path.splitext(target)[0] dst = os.path.join(dst_dir, base + ".css") if base in seennames: self.logger.error( 'Duplicate filenames for SASS compiled files: {0} and ' '{1} (both compile to {2})'.format(seennames[base], target, base + ".css")) else: seennames.update({base: target}) yield { 'basename': self.name, 'name': dst, 'targets': [dst], 'file_dep': deps, 'task_dep': ['prepare_sass_sources'], 'actions': ((compile_target, [target, dst]), ), 'uptodate': [utils.config_changed(kw)], 'clean': True }
def gen_tasks(self): """Generate CSS out of Sass sources.""" self.logger = utils.get_logger('build_sass', utils.STDERR_HANDLER) self.compiler_name = self.site.config['SASS_COMPILER'] self.compiler_options = self.site.config['SASS_OPTIONS'] kw = { 'cache_folder': self.site.config['CACHE_FOLDER'], 'themes': self.site.THEMES, } tasks = {} # Find where in the theme chain we define the Sass targets # There can be many *.sass/*.scss in the folder, but we only # will build the ones listed in sass/targets if os.path.isfile(os.path.join(self.sources_folder, "targets")): targets_path = os.path.join(self.sources_folder, "targets") else: targets_path = utils.get_asset_path( os.path.join(self.sources_folder, "targets"), self.site.THEMES) try: with codecs.open(targets_path, "rb", "utf-8") as inf: targets = [x.strip() for x in inf.readlines()] except Exception: targets = [] for task in utils.copy_tree( self.sources_folder, os.path.join(kw['cache_folder'], self.sources_folder)): if task['name'] in tasks: continue task['basename'] = 'prepare_sass_sources' tasks[task['name']] = task yield task for theme_name in kw['themes']: src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder) for task in utils.copy_tree( src, os.path.join(kw['cache_folder'], self.sources_folder)): if task['name'] in tasks: continue task['basename'] = 'prepare_sass_sources' tasks[task['name']] = task yield task # Build targets and write CSS files dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets', 'css') # Make everything depend on all sources, rough but enough deps = [] for task in tasks.keys(): for ext in self.sources_ext: if task.endswith(ext): deps.append(task) def compile_target(target, dst): utils.makedirs(dst_dir) run_in_shell = sys.platform == 'win32' src = os.path.join(kw['cache_folder'], self.sources_folder, target) try: compiled = subprocess.check_output( [self.compiler_name] + self.compiler_options + [src], shell=run_in_shell) except OSError: utils.req_missing([self.compiler_name], 'build Sass files (and use this theme)', False, False) with open(dst, "wb+") as outf: outf.write(compiled) yield self.group_task() # We can have file conflicts. This is a way to prevent them. # I orignally wanted to use sets and their cannot-have-duplicates # magic, but I decided not to do this so we can show the user # what files were problematic. # If we didn’t do this, there would be a cryptic message from doit # instead. seennames = {} for target in targets: base = os.path.splitext(target)[0] dst = os.path.join(dst_dir, base + ".css") if base in seennames: self.logger.error( 'Duplicate filenames for Sass compiled files: {0} and ' '{1} (both compile to {2})'.format(seennames[base], target, base + ".css")) else: seennames.update({base: target}) yield { 'basename': self.name, 'name': dst, 'targets': [dst], 'file_dep': deps, 'task_dep': ['prepare_sass_sources'], 'actions': ((compile_target, [target, dst]), ), 'uptodate': [utils.config_changed(kw)], 'clean': True }
def gen_tasks(self): """Create tasks to copy the assets of the whole theme chain. If a file is present on two themes, use the version from the "youngest" theme. """ kw = { "themes": self.site.THEMES, "translations": self.site.translations, "files_folders": self.site.config['FILES_FOLDERS'], "output_folder": self.site.config['OUTPUT_FOLDER'], "filters": self.site.config['FILTERS'], "code_color_scheme": self.site.config['CODE_COLOR_SCHEME'], "code.css_selectors": ['pre.code', '.highlight pre'], "code.css_head": '/* code.css file generated by Nikola */\n', "code.css_close": "\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n", } tasks = {} code_css_path = os.path.join(kw['output_folder'], 'assets', 'css', 'code.css') code_css_input = utils.get_asset_path('assets/css/code.css', themes=kw['themes'], files_folders=kw['files_folders'], output_dir=None) yield self.group_task() main_theme = utils.get_theme_path(kw['themes'][0]) theme_ini = utils.parse_theme_meta(main_theme) if theme_ini: ignored_assets = theme_ini.get("Nikola", "ignored_assets", fallback='').split(',') ignore_colorbox_i18n = theme_ini.get("Nikola", "ignore_colorbox_i18n", fallback="unused") else: ignored_assets = [] ignore_colorbox_i18n = "unused" if ignore_colorbox_i18n == "unused": # Check what colorbox languages we need so we can ignore the rest needed_colorbox_languages = [LEGAL_VALUES['COLORBOX_LOCALES'][i] for i in kw['translations']] needed_colorbox_languages = [i for i in needed_colorbox_languages if i] # remove '' for en # ignored_filenames is passed to copy_tree to avoid creating # directories. Since ignored_assets are full paths, and copy_tree # works on single filenames, we can’t use that here. if not needed_colorbox_languages: ignored_filenames = set(["colorbox-i18n"]) else: ignored_filenames = set() for theme_name in kw['themes']: src = os.path.join(utils.get_theme_path(theme_name), 'assets') dst = os.path.join(kw['output_folder'], 'assets') for task in utils.copy_tree(src, dst, ignored_filenames=ignored_filenames): asset_name = os.path.relpath(task['name'], dst) if task['name'] in tasks or asset_name in ignored_assets: continue elif asset_name.startswith(_COLORBOX_PREFIX): if ignore_colorbox_i18n == "all" or ignore_colorbox_i18n is True: continue colorbox_lang = asset_name[_COLORBOX_SLICE] if ignore_colorbox_i18n == "unused" and colorbox_lang not in needed_colorbox_languages: continue tasks[task['name']] = task task['uptodate'] = [utils.config_changed(kw, 'nikola.plugins.task.copy_assets')] task['basename'] = self.name if code_css_input: if 'file_dep' not in task: task['file_dep'] = [] task['file_dep'].append(code_css_input) yield utils.apply_filters(task, kw['filters']) # Check whether or not there is a code.css file around. if not code_css_input and kw['code_color_scheme']: def create_code_css(): from pygments.formatters import get_formatter_by_name formatter = get_formatter_by_name('html', style=kw["code_color_scheme"]) utils.makedirs(os.path.dirname(code_css_path)) with io.open(code_css_path, 'w+', encoding='utf8') as outf: outf.write(kw["code.css_head"]) outf.write(formatter.get_style_defs(kw["code.css_selectors"])) outf.write(kw["code.css_close"]) if os.path.exists(code_css_path): with io.open(code_css_path, 'r', encoding='utf-8') as fh: testcontents = fh.read(len(kw["code.css_head"])) == kw["code.css_head"] else: testcontents = False task = { 'basename': self.name, 'name': code_css_path, 'targets': [code_css_path], 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.copy_assets'), testcontents], 'actions': [(create_code_css, [])], 'clean': True, } yield utils.apply_filters(task, kw['filters'])
def _execute(self, options, args): """Given a swatch name and a parent theme, creates a custom theme.""" name = options['name'] swatch = options['swatch'] if not swatch: LOGGER.error('The -s option is mandatory') return 1 parent = options['parent'] version = '4' # Check which Bootstrap version to use themes = utils.get_theme_chain(parent, self.site.themes_dirs) if _check_for_theme('bootstrap', themes) or _check_for_theme('bootstrap-jinja', themes): version = '2' elif _check_for_theme('bootstrap3', themes) or _check_for_theme('bootstrap3-jinja', themes): version = '3' elif _check_for_theme('bootstrap4', themes) or _check_for_theme('bootstrap4-jinja', themes): version = '4' elif not _check_for_theme('bootstrap4', themes) and not _check_for_theme('bootstrap4-jinja', themes): LOGGER.warn( '"subtheme" only makes sense for themes that use bootstrap') elif _check_for_theme('bootstrap3-gradients', themes) or _check_for_theme('bootstrap3-gradients-jinja', themes): LOGGER.warn( '"subtheme" doesn\'t work well with the bootstrap3-gradients family') LOGGER.info("Creating '{0}' theme from '{1}' and '{2}'".format( name, swatch, parent)) utils.makedirs(os.path.join('themes', name, 'assets', 'css')) for fname in ('bootstrap.min.css', 'bootstrap.css'): if swatch in [ 'bubblegum', 'business-tycoon', 'charming', 'daydream', 'executive-suite', 'good-news', 'growth', 'harbor', 'hello-world', 'neon-glow', 'pleasant', 'retro', 'vibrant-sea', 'wizardry']: # Hackerthemes LOGGER.info( 'Hackertheme-based subthemes often require you use a custom font for full effect.') if version != '4': LOGGER.error( 'The hackertheme subthemes are only available for Bootstrap 4.') return 1 if fname == 'bootstrap.css': url = 'https://raw.githubusercontent.com/HackerThemes/theme-machine/master/dist/{swatch}/css/bootstrap4-{swatch}.css'.format( swatch=swatch) else: url = 'https://raw.githubusercontent.com/HackerThemes/theme-machine/master/dist/{swatch}/css/bootstrap4-{swatch}.min.css'.format( swatch=swatch) else: # Bootswatch url = 'https://bootswatch.com' if version: url += '/' + version url = '/'.join((url, swatch, fname)) LOGGER.info("Downloading: " + url) r = requests.get(url) if r.status_code > 299: LOGGER.error('Error {} getting {}', r.status_code, url) return 1 data = r.text with open(os.path.join('themes', name, 'assets', 'css', fname), 'w+') as output: output.write(data) with open(os.path.join('themes', name, '%s.theme' % name), 'w+') as output: parent_theme_data_path = utils.get_asset_path( '%s.theme' % parent, themes) cp = configparser.ConfigParser() cp.read(parent_theme_data_path) cp['Theme']['parent'] = parent cp['Family'] = {'family': cp['Family']['family']} cp.write(output) LOGGER.notice( 'Theme created. Change the THEME setting to "{0}" to use it.'.format(name))
def gen_tasks(self): """Create tasks to copy the assets of the whole theme chain. If a file is present on two themes, use the version from the "youngest" theme. """ kw = { "themes": self.site.THEMES, "translations": self.site.translations, "files_folders": self.site.config['FILES_FOLDERS'], "output_folder": self.site.config['OUTPUT_FOLDER'], "filters": self.site.config['FILTERS'], "code_color_scheme": self.site.config['CODE_COLOR_SCHEME'], "code.css_selectors": ['pre.code', '.code .codetable', '.highlight pre'], "code.css_wrappers": ['.highlight', '.code'], "code.css_head": '/* code.css file generated by Nikola */\n', "code.css_close": ("\ntable.codetable, table.highlighttable { width: 100%;}\n" ".codetable td.linenos, td.linenos { text-align: right; width: 3.5em; " "padding-right: 0.5em; background: rgba(127, 127, 127, 0.2) }\n" ".codetable td.code, td.code { padding-left: 0.5em; }\n"), } tasks = {} code_css_path = os.path.join(kw['output_folder'], 'css', 'code.css') code_css_input = utils.get_asset_path( 'assets/css/code.css', themes=kw['themes'], files_folders=kw['files_folders'], output_dir=None) yield self.group_task() main_theme = utils.get_theme_path(kw['themes'][0]) theme_ini = utils.parse_theme_meta(main_theme) if theme_ini: ignored_assets = theme_ini.get("Nikola", "ignored_assets", fallback='').split(',') ignored_assets = [ os.path.normpath(asset_name.strip()) for asset_name in ignored_assets ] else: ignored_assets = [] for theme_name in kw['themes']: src = os.path.join(utils.get_theme_path(theme_name), 'assets') dst = kw['output_folder'] for task in utils.copy_tree(src, dst): asset_name = os.path.relpath(task['name'], dst) if task['name'] in tasks or asset_name in ignored_assets: continue tasks[task['name']] = task task['uptodate'] = [ utils.config_changed(kw, 'nikola.plugins.task.copy_assets') ] task['basename'] = self.name if code_css_input: if 'file_dep' not in task: task['file_dep'] = [] task['file_dep'].append(code_css_input) yield utils.apply_filters(task, kw['filters']) # Check whether or not there is a code.css file around. if not code_css_input and kw['code_color_scheme']: def create_code_css(): formatter = BetterHtmlFormatter(style=kw["code_color_scheme"]) utils.makedirs(os.path.dirname(code_css_path)) with io.open(code_css_path, 'w+', encoding='utf-8') as outf: outf.write(kw["code.css_head"]) outf.write( formatter.get_style_defs(kw["code.css_selectors"], kw["code.css_wrappers"])) outf.write(kw["code.css_close"]) if os.path.exists(code_css_path): with io.open(code_css_path, 'r', encoding='utf-8-sig') as fh: testcontents = fh.read(len( kw["code.css_head"])) == kw["code.css_head"] else: testcontents = False task = { 'basename': self.name, 'name': code_css_path, 'targets': [code_css_path], 'uptodate': [ utils.config_changed(kw, 'nikola.plugins.task.copy_assets'), testcontents ], 'actions': [(create_code_css, [])], 'clean': True, } yield utils.apply_filters(task, kw['filters'])
def gen_tasks(self): """Create tasks to copy the assets of the whole theme chain. If a file is present on two themes, use the version from the "youngest" theme. """ kw = { "themes": self.site.THEMES, "translations": self.site.translations, "files_folders": self.site.config['FILES_FOLDERS'], "output_folder": self.site.config['OUTPUT_FOLDER'], "filters": self.site.config['FILTERS'], "code_color_scheme": self.site.config['CODE_COLOR_SCHEME'], "code.css_selectors": ['pre.code', '.highlight pre'], "code.css_head": '/* code.css file generated by Nikola */\n', "code.css_close": "\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n", } tasks = {} code_css_path = os.path.join(kw['output_folder'], 'assets', 'css', 'code.css') code_css_input = utils.get_asset_path( 'assets/css/code.css', themes=kw['themes'], files_folders=kw['files_folders'], output_dir=None) yield self.group_task() main_theme = utils.get_theme_path(kw['themes'][0]) theme_ini = utils.parse_theme_meta(main_theme) if theme_ini: ignored_assets = theme_ini.get("Nikola", "ignored_assets", fallback='').split(',') ignore_colorbox_i18n = theme_ini.get("Nikola", "ignore_colorbox_i18n", fallback="unused") else: ignored_assets = [] ignore_colorbox_i18n = "unused" if ignore_colorbox_i18n == "unused": # Check what colorbox languages we need so we can ignore the rest needed_colorbox_languages = [ LEGAL_VALUES['COLORBOX_LOCALES'][i] for i in kw['translations'] ] needed_colorbox_languages = [ i for i in needed_colorbox_languages if i ] # remove '' for en # ignored_filenames is passed to copy_tree to avoid creating # directories. Since ignored_assets are full paths, and copy_tree # works on single filenames, we can’t use that here. if not needed_colorbox_languages: ignored_filenames = set(["colorbox-i18n"]) else: ignored_filenames = set() for theme_name in kw['themes']: src = os.path.join(utils.get_theme_path(theme_name), 'assets') dst = os.path.join(kw['output_folder'], 'assets') for task in utils.copy_tree(src, dst, ignored_filenames=ignored_filenames): asset_name = os.path.relpath(task['name'], dst) if task['name'] in tasks or asset_name in ignored_assets: continue elif asset_name.startswith(_COLORBOX_PREFIX): if ignore_colorbox_i18n == "all" or ignore_colorbox_i18n is True: continue colorbox_lang = asset_name[_COLORBOX_SLICE] if ignore_colorbox_i18n == "unused" and colorbox_lang not in needed_colorbox_languages: continue tasks[task['name']] = task task['uptodate'] = [ utils.config_changed(kw, 'nikola.plugins.task.copy_assets') ] task['basename'] = self.name if code_css_input: if 'file_dep' not in task: task['file_dep'] = [] task['file_dep'].append(code_css_input) yield utils.apply_filters(task, kw['filters']) # Check whether or not there is a code.css file around. if not code_css_input and kw['code_color_scheme']: def create_code_css(): from pygments.formatters import get_formatter_by_name formatter = get_formatter_by_name( 'html', style=kw["code_color_scheme"]) utils.makedirs(os.path.dirname(code_css_path)) with io.open(code_css_path, 'w+', encoding='utf8') as outf: outf.write(kw["code.css_head"]) outf.write( formatter.get_style_defs(kw["code.css_selectors"])) outf.write(kw["code.css_close"]) if os.path.exists(code_css_path): with io.open(code_css_path, 'r', encoding='utf-8') as fh: testcontents = fh.read(len( kw["code.css_head"])) == kw["code.css_head"] else: testcontents = False task = { 'basename': self.name, 'name': code_css_path, 'targets': [code_css_path], 'uptodate': [ utils.config_changed(kw, 'nikola.plugins.task.copy_assets'), testcontents ], 'actions': [(create_code_css, [])], 'clean': True, } yield utils.apply_filters(task, kw['filters'])
def gen_tasks(self): """Bundle assets using WebAssets.""" kw = { 'filters': self.site.config['FILTERS'], 'output_folder': self.site.config['OUTPUT_FOLDER'], 'cache_folder': self.site.config['CACHE_FOLDER'], 'theme_bundles': get_theme_bundles(self.site.THEMES), 'themes': self.site.THEMES, 'files_folders': self.site.config['FILES_FOLDERS'], 'code_color_scheme': self.site.config['CODE_COLOR_SCHEME'], } def build_bundle(output, inputs): out_dir = os.path.join(kw['output_folder'], os.path.dirname(output)) inputs = [os.path.relpath(i, out_dir) for i in inputs if os.path.isfile(i)] cache_dir = os.path.join(kw['cache_folder'], 'webassets') utils.makedirs(cache_dir) env = webassets.Environment(out_dir, os.path.dirname(output), cache=cache_dir) if inputs: bundle = webassets.Bundle(*inputs, output=os.path.basename(output)) env.register(output, bundle) # This generates the file try: env[output].build(force=True) except Exception as e: self.logger.error("Failed to build bundles.") self.logger.exception(e) self.logger.notice("Try running ``nikola clean`` and building again.") else: with open(os.path.join(out_dir, os.path.basename(output)), 'wb+'): pass # Create empty file yield self.group_task() if (webassets is not None and self.site.config['USE_BUNDLES'] is not False): for name, _files in kw['theme_bundles'].items(): output_path = os.path.join(kw['output_folder'], name) dname = os.path.dirname(name) files = [] for fname in _files: # paths are relative to dirname files.append(os.path.join(dname, fname)) file_dep = [os.path.join(kw['output_folder'], fname) for fname in files if utils.get_asset_path( fname, self.site.THEMES, self.site.config['FILES_FOLDERS'], output_dir=kw['output_folder']) or fname == os.path.join('assets', 'css', 'code.css')] # code.css will be generated by us if it does not exist in # FILES_FOLDERS or theme assets. It is guaranteed that the # generation will happen before this task. task = { 'file_dep': list(file_dep), 'task_dep': ['copy_assets', 'copy_files'], 'basename': str(self.name), 'name': str(output_path), 'actions': [(build_bundle, (name, file_dep))], 'targets': [output_path], 'uptodate': [ utils.config_changed({ 1: kw, 2: file_dep }, 'nikola.plugins.task.bundles')], 'clean': True, } yield utils.apply_filters(task, kw['filters'])
def gen_tasks(self): """Bundle assets using WebAssets.""" kw = { 'filters': self.site.config['FILTERS'], 'output_folder': self.site.config['OUTPUT_FOLDER'], 'cache_folder': self.site.config['CACHE_FOLDER'], 'theme_bundles': get_theme_bundles(self.site.THEMES), 'themes': self.site.THEMES, 'files_folders': self.site.config['FILES_FOLDERS'], 'code_color_scheme': self.site.config['CODE_COLOR_SCHEME'], } def build_bundle(output, inputs): out_dir = os.path.join(kw['output_folder'], os.path.dirname(output)) inputs = [ os.path.relpath(i, out_dir) for i in inputs if os.path.isfile(i) ] cache_dir = os.path.join(kw['cache_folder'], 'webassets') utils.makedirs(cache_dir) env = webassets.Environment(out_dir, os.path.dirname(output), cache=cache_dir) if inputs: bundle = webassets.Bundle(*inputs, output=os.path.basename(output)) env.register(output, bundle) # This generates the file try: env[output].build(force=True) except Exception as e: self.logger.error("Failed to build bundles.") self.logger.exception(e) self.logger.notice( "Try running ``nikola clean`` and building again.") else: with open(os.path.join(out_dir, os.path.basename(output)), 'wb+'): pass # Create empty file yield self.group_task() if (webassets is not None and self.site.config['USE_BUNDLES'] is not False): for name, _files in kw['theme_bundles'].items(): output_path = os.path.join(kw['output_folder'], name) dname = os.path.dirname(name) files = [] for fname in _files: # paths are relative to dirname files.append(os.path.join(dname, fname)) file_dep = [ os.path.join(kw['output_folder'], fname) for fname in files if utils.get_asset_path(fname, self.site.THEMES, self.site.config['FILES_FOLDERS'], output_dir=kw['output_folder']) or fname == os.path.join('assets', 'css', 'code.css') ] # code.css will be generated by us if it does not exist in # FILES_FOLDERS or theme assets. It is guaranteed that the # generation will happen before this task. task = { 'file_dep': list(file_dep), 'task_dep': ['copy_assets', 'copy_files'], 'basename': str(self.name), 'name': str(output_path), 'actions': [(build_bundle, (name, file_dep))], 'targets': [output_path], 'uptodate': [ utils.config_changed({ 1: kw, 2: file_dep }, 'nikola.plugins.task.bundles') ], 'clean': True, } yield utils.apply_filters(task, kw['filters'])
def gen_tasks(self): """Generate CSS out of LESS sources.""" self.compiler_name = self.site.config['LESS_COMPILER'] self.compiler_options = self.site.config['LESS_OPTIONS'] kw = { 'cache_folder': self.site.config['CACHE_FOLDER'], 'themes': self.site.THEMES, } tasks = {} # Find where in the theme chain we define the LESS targets # There can be many *.less in the folder, but we only will build # the ones listed in less/targets if os.path.isfile(os.path.join(self.sources_folder, "targets")): targets_path = os.path.join(self.sources_folder, "targets") else: targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES) try: with codecs.open(targets_path, "rb", "utf-8") as inf: targets = [x.strip() for x in inf.readlines()] except Exception: targets = [] for task in utils.copy_tree(self.sources_folder, os.path.join(kw['cache_folder'], self.sources_folder)): if task['name'] in tasks: continue task['basename'] = 'prepare_less_sources' tasks[task['name']] = task yield task for theme_name in kw['themes']: src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder) for task in utils.copy_tree(src, os.path.join(kw['cache_folder'], self.sources_folder)): if task['name'] in tasks: continue task['basename'] = 'prepare_less_sources' tasks[task['name']] = task yield task # Build targets and write CSS files dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets', 'css') # Make everything depend on all sources, rough but enough deps = [] for task in tasks.keys(): if task.endswith(self.sources_ext): deps.append(task) def compile_target(target, dst): utils.makedirs(dst_dir) src = os.path.join(kw['cache_folder'], self.sources_folder, target) run_in_shell = sys.platform == 'win32' try: compiled = subprocess.check_output([self.compiler_name] + self.compiler_options + [src], shell=run_in_shell) except OSError: utils.req_missing([self.compiler_name], 'build LESS files (and use this theme)', False, False) with open(dst, "wb+") as outf: outf.write(compiled) yield self.group_task() for target in targets: dst = os.path.join(dst_dir, target.replace(self.sources_ext, ".css")) yield { 'basename': self.name, 'name': dst, 'targets': [dst], 'file_dep': deps, 'task_dep': ['prepare_less_sources'], 'actions': ((compile_target, [target, dst]), ), 'uptodate': [utils.config_changed(kw)], 'clean': True }
def gen_tasks(self): """Bundle assets.""" kw = { 'filters': self.site.config['FILTERS'], 'output_folder': self.site.config['OUTPUT_FOLDER'], 'cache_folder': self.site.config['CACHE_FOLDER'], 'theme_bundles': get_theme_bundles(self.site.THEMES), 'themes': self.site.THEMES, 'files_folders': self.site.config['FILES_FOLDERS'], 'code_color_scheme': self.site.config['CODE_COLOR_SCHEME'], } def build_bundle(output, inputs): out_dir = os.path.join(kw['output_folder'], os.path.dirname(output)) inputs = [ os.path.join( out_dir, os.path.relpath(i, out_dir)) for i in inputs if os.path.isfile(i) ] with open(os.path.join(out_dir, os.path.basename(output)), 'wb+') as out_fh: for i in inputs: with open(i, 'rb') as in_fh: shutil.copyfileobj(in_fh, out_fh) out_fh.write(b'\n') yield self.group_task() if self.site.config['USE_BUNDLES']: for name, _files in kw['theme_bundles'].items(): output_path = os.path.join(kw['output_folder'], name) dname = os.path.dirname(name) files = [] for fname in _files: # paths are relative to dirname files.append(os.path.join(dname, fname)) file_dep = [os.path.join(kw['output_folder'], fname) for fname in files if utils.get_asset_path( fname, self.site.THEMES, self.site.config['FILES_FOLDERS'], output_dir=kw['output_folder']) or fname == os.path.join('assets', 'css', 'code.css')] # code.css will be generated by us if it does not exist in # FILES_FOLDERS or theme assets. It is guaranteed that the # generation will happen before this task. task = { 'file_dep': list(file_dep), 'task_dep': ['copy_assets', 'copy_files'], 'basename': str(self.name), 'name': str(output_path), 'actions': [(build_bundle, (name, file_dep))], 'targets': [output_path], 'uptodate': [ utils.config_changed({ 1: kw, 2: file_dep }, 'nikola.plugins.task.bundles')], 'clean': True, } yield utils.apply_filters(task, kw['filters'])