diff options
Diffstat (limited to 'nikola/plugins')
45 files changed, 615 insertions, 276 deletions
diff --git a/nikola/plugins/basic_import.py b/nikola/plugins/basic_import.py index 0d94d16..27c0eb4 100644 --- a/nikola/plugins/basic_import.py +++ b/nikola/plugins/basic_import.py @@ -48,8 +48,8 @@ class ImportMixin(object): name = "import_mixin" needs_config = False - doc_usage = "[options] wordpress_export_file" - doc_purpose = "import a wordpress dump." + doc_usage = "[options] export_file" + doc_purpose = "import a dump from a different engine." cmd_options = [ { 'name': 'output_folder', @@ -93,7 +93,7 @@ class ImportMixin(object): else: self.import_into_existing_site = True utils.LOGGER.notice('The folder {0} already exists - assuming that this is a ' - 'already existing nikola site.'.format(self.output_folder)) + 'already existing Nikola site.'.format(self.output_folder)) filename = os.path.join(os.path.dirname(utils.__file__), 'conf.py.in') # The 'strict_undefined=True' will give the missing symbol name if any, @@ -151,7 +151,7 @@ class ImportMixin(object): time=datetime.datetime.now().strftime('%Y%m%d_%H%M%S'), name=self.name) config_output_path = os.path.join(self.output_folder, filename) - utils.LOGGER.notice('Configuration will be written to: {0}'.format(config_output_path)) + utils.LOGGER.info('Configuration will be written to: {0}'.format(config_output_path)) return config_output_path diff --git a/nikola/plugins/command/auto.py b/nikola/plugins/command/auto.py index 01116d1..d707d53 100644 --- a/nikola/plugins/command/auto.py +++ b/nikola/plugins/command/auto.py @@ -34,7 +34,7 @@ from nikola.plugin_categories import Command from nikola.utils import req_missing -class Auto(Command): +class CommandAuto(Command): """Start debugging console.""" name = "auto" doc_purpose = "automatically detect site changes, rebuild and optionally refresh a browser" @@ -61,26 +61,26 @@ class Auto(Command): try: from livereload import Server except ImportError: - req_missing(['livereload>=2.0.0'], 'use the "auto" command') + req_missing(['livereload==2.1.0'], 'use the "auto" command') return - # Run an initial build so we are uptodate + # Run an initial build so we are up-to-date subprocess.call(("nikola", "build")) port = options and options.get('port') server = Server() - server.watch('conf.py') - server.watch('themes/') - server.watch('templates/') + server.watch('conf.py', 'nikola build') + server.watch('themes/', 'nikola build') + server.watch('templates/', 'nikola build') server.watch(self.site.config['GALLERY_PATH']) for item in self.site.config['post_pages']: - server.watch(os.path.dirname(item[0])) + server.watch(os.path.dirname(item[0]), 'nikola build') for item in self.site.config['FILES_FOLDERS']: - server.watch(os.path.dirname(item)) + server.watch(os.path.dirname(item), 'nikola build') out_folder = self.site.config['OUTPUT_FOLDER'] if options and options.get('browser'): webbrowser.open('http://localhost:{0}'.format(port)) - server.serve(port, out_folder) + server.serve(port, None, out_folder) diff --git a/nikola/plugins/command/bootswatch_theme.py b/nikola/plugins/command/bootswatch_theme.py index 94f37f2..82c47d2 100644 --- a/nikola/plugins/command/bootswatch_theme.py +++ b/nikola/plugins/command/bootswatch_theme.py @@ -86,12 +86,14 @@ class CommandBootswatchTheme(Command): version = '2' elif 'bootstrap' not in themes: LOGGER.warn('"bootswatch_theme" only makes sense for themes that use bootstrap') + elif 'bootstrap3-gradients' in themes or 'bootstrap3-gradients-jinja' in themes: + LOGGER.warn('"bootswatch_theme" doesn\'t work well with the bootstrap3-gradients family') - LOGGER.notice("Creating '{0}' theme from '{1}' and '{2}'".format(name, swatch, parent)) + 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'): url = '/'.join(('http://bootswatch.com', version, swatch, fname)) - LOGGER.notice("Downloading: " + url) + LOGGER.info("Downloading: " + url) data = requests.get(url).text with open(os.path.join('themes', name, 'assets', 'css', fname), 'wb+') as output: @@ -99,5 +101,4 @@ class CommandBootswatchTheme(Command): with open(os.path.join('themes', name, 'parent'), 'wb+') as output: output.write(parent.encode('utf-8')) - LOGGER.notice('Theme created. Change the THEME setting to "{0}" to use ' - 'it.'.format(name)) + LOGGER.notice('Theme created. Change the THEME setting to "{0}" to use it.'.format(name)) diff --git a/nikola/plugins/command/check.py b/nikola/plugins/command/check.py index a7e8c13..26db321 100644 --- a/nikola/plugins/command/check.py +++ b/nikola/plugins/command/check.py @@ -102,6 +102,14 @@ class CommandCheck(Command): 'default': False, 'help': 'List possible source files for files with broken links.', }, + { + 'name': 'verbose', + 'long': 'verbose', + 'short': 'v', + 'type': bool, + 'default': False, + 'help': 'Be more verbose.', + }, ] def _execute(self, options, args): @@ -112,6 +120,10 @@ class CommandCheck(Command): if not options['links'] and not options['files'] and not options['clean']: print(self.help()) return False + if options['verbose']: + self.logger.level = 1 + else: + self.logger.level = 4 if options['links']: failure = self.scan_links(options['find_sources']) if options['files']: @@ -126,6 +138,8 @@ class CommandCheck(Command): def analyze(self, task, find_sources=False): rv = False self.whitelist = [re.compile(x) for x in self.site.config['LINK_CHECK_WHITELIST']] + base_url = urlparse(self.site.config['BASE_URL']) + url_type = self.site.config['URL_TYPE'] try: filename = task.split(":")[-1] d = lxml.html.fromstring(open(filename).read()) @@ -134,31 +148,51 @@ class CommandCheck(Command): if target == "#": continue parsed = urlparse(target) - if parsed.scheme or target.startswith('//'): + + # Absolute links when using only paths, skip. + if (parsed.scheme or target.startswith('//')) and url_type in ('rel_path', 'full_path'): continue + + # Absolute links to other domains, skip + if (parsed.scheme or target.startswith('//')) and parsed.netloc != base_url.netloc: + continue + if parsed.fragment: target = target.split('#')[0] - target_filename = os.path.abspath( - os.path.join(os.path.dirname(filename), unquote(target))) + if url_type == 'rel_path': + target_filename = os.path.abspath( + os.path.join(os.path.dirname(filename), unquote(target))) + + elif url_type in ('full_path', 'absolute'): + target_filename = os.path.abspath( + os.path.join(os.path.dirname(filename), parsed.path)) + if parsed.path.endswith('/'): # abspath removes trailing slashes + target_filename += '/{0}'.format(self.site.config['INDEX_FILE']) + if target_filename.startswith(base_url.path): + target_filename = target_filename[len(base_url.path):] + target_filename = os.path.join(self.site.config['OUTPUT_FOLDER'], target_filename) + if any(re.match(x, target_filename) for x in self.whitelist): continue elif target_filename not in self.existing_targets: if os.path.exists(target_filename): + self.logger.notice("Good link {0} => {1}".format(target, target_filename)) self.existing_targets.add(target_filename) else: rv = True - self.logger.warn("Broken link in {0}: ".format(filename), target) + self.logger.warn("Broken link in {0}: {1}".format(filename, target)) if find_sources: self.logger.warn("Possible sources:") self.logger.warn(os.popen('nikola list --deps ' + task, 'r').read()) self.logger.warn("===============================\n") except Exception as exc: - self.logger.error("Error with:", filename, exc) + self.logger.error("Error with: {0} {1}".format(filename, exc)) return rv def scan_links(self, find_sources=False): - self.logger.notice("Checking Links:") - self.logger.notice("===============") + self.logger.info("Checking Links:") + self.logger.info("===============\n") + self.logger.notice("{0} mode".format(self.site.config['URL_TYPE'])) failure = False for task in os.popen('nikola list --all', 'r').readlines(): task = task.strip() @@ -170,13 +204,13 @@ class CommandCheck(Command): if self.analyze(task, find_sources): failure = True if not failure: - self.logger.notice("All links checked.") + self.logger.info("All links checked.") return failure def scan_files(self): failure = False - self.logger.notice("Checking Files:") - self.logger.notice("===============\n") + self.logger.info("Checking Files:") + self.logger.info("===============\n") only_on_output, only_on_input = real_scan_files(self.site) # Ignore folders @@ -195,7 +229,7 @@ class CommandCheck(Command): for f in only_on_input: self.logger.warn(f) if not failure: - self.logger.notice("All files checked.") + self.logger.info("All files checked.") return failure def clean_files(self): diff --git a/nikola/plugins/command/console.py b/nikola/plugins/command/console.py index e66b650..b0a8958 100644 --- a/nikola/plugins/command/console.py +++ b/nikola/plugins/command/console.py @@ -35,7 +35,7 @@ from nikola.utils import get_logger, STDERR_HANDLER LOGGER = get_logger('console', STDERR_HANDLER) -class Console(Command): +class CommandConsole(Command): """Start debugging console.""" name = "console" shells = ['ipython', 'bpython', 'plain'] diff --git a/nikola/plugins/command/deploy.py b/nikola/plugins/command/deploy.py index eb5787e..bd1c15f 100644 --- a/nikola/plugins/command/deploy.py +++ b/nikola/plugins/command/deploy.py @@ -25,7 +25,6 @@ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. from __future__ import print_function -from ast import literal_eval import codecs from datetime import datetime import os @@ -40,8 +39,8 @@ from nikola.plugin_categories import Command from nikola.utils import remove_file, get_logger -class Deploy(Command): - """Deploy site. """ +class CommandDeploy(Command): + """Deploy site.""" name = "deploy" doc_usage = "" @@ -76,7 +75,7 @@ class Deploy(Command): undeployed_posts.append(post) for command in self.site.config['DEPLOY_COMMANDS']: - self.logger.notice("==> {0}".format(command)) + self.logger.info("==> {0}".format(command)) try: subprocess.check_call(command, shell=True) except subprocess.CalledProcessError as e: @@ -84,26 +83,22 @@ class Deploy(Command): 'returned {1}'.format(e.cmd, e.returncode)) sys.exit(e.returncode) - self.logger.notice("Successful deployment") - tzinfo = pytz.timezone(self.site.config['TIMEZONE']) + self.logger.info("Successful deployment") try: - with open(timestamp_path, 'rb') as inf: - last_deploy = literal_eval(inf.read().strip()) - if tzinfo: - last_deploy = last_deploy.replace(tzinfo=tzinfo) + with codecs.open(timestamp_path, 'rb', 'utf8') as inf: + last_deploy = datetime.strptime(inf.read().strip(), "%Y-%m-%dT%H:%M:%S.%f") clean = False - except Exception: + except (IOError, Exception) as e: + self.logger.debug("Problem when reading `{0}`: {1}".format(timestamp_path, e)) last_deploy = datetime(1970, 1, 1) - if tzinfo: - last_deploy = last_deploy.replace(tzinfo=tzinfo) clean = True - new_deploy = datetime.now() + new_deploy = datetime.utcnow() self._emit_deploy_event(last_deploy, new_deploy, clean, undeployed_posts) # Store timestamp of successful deployment with codecs.open(timestamp_path, 'wb+', 'utf8') as outf: - outf.write(repr(new_deploy)) + outf.write(new_deploy.isoformat()) def _emit_deploy_event(self, last_deploy, new_deploy, clean=False, undeployed=None): """ Emit events for all timeline entries newer than last deploy. @@ -129,9 +124,11 @@ class Deploy(Command): 'undeployed': undeployed } + tzinfo = pytz.timezone(self.site.config['TIMEZONE']) + deployed = [ entry for entry in self.site.timeline - if entry.date > last_deploy and entry not in undeployed + if entry.date > (last_deploy.replace(tzinfo=tzinfo) if tzinfo else last_deploy) and entry not in undeployed ] event['deployed'] = deployed diff --git a/nikola/plugins/command/import_blogger.py b/nikola/plugins/command/import_blogger.py index ea12b4a..dd629c4 100644 --- a/nikola/plugins/command/import_blogger.py +++ b/nikola/plugins/command/import_blogger.py @@ -43,6 +43,7 @@ from nikola.plugin_categories import Command from nikola import utils from nikola.utils import req_missing from nikola.plugins.basic_import import ImportMixin +from nikola.plugins.command.init import SAMPLE_CONF, prepare_config LOGGER = utils.get_logger('import_blogger', utils.STDERR_HANDLER) @@ -95,8 +96,8 @@ class CommandImportBlogger(Command, ImportMixin): conf_out_path = self.get_configuration_output_path() # if it tracebacks here, look a comment in # basic_import.Import_Mixin.generate_base_site - conf_termplate_render = conf_template.render(**self.context) - self.write_configuration(conf_out_path, conf_termplate_render) + conf_template_render = conf_template.render(**prepare_config(self.context)) + self.write_configuration(conf_out_path, conf_template_render) @classmethod def get_channel_from_file(cls, filename): @@ -106,8 +107,7 @@ class CommandImportBlogger(Command, ImportMixin): @staticmethod def populate_context(channel): - # may need changes when the template conf.py.in changes - context = {} + context = SAMPLE_CONF.copy() context['DEFAULT_LANG'] = 'en' # blogger doesn't include the language # in the dump context['BLOG_TITLE'] = channel.feed.title @@ -131,7 +131,6 @@ class CommandImportBlogger(Command, ImportMixin): "html": ('.html', '.htm') } ''' - context['THEME'] = 'bootstrap3' return context diff --git a/nikola/plugins/command/import_feed.py b/nikola/plugins/command/import_feed.py index 70a5cd5..ee59277 100644 --- a/nikola/plugins/command/import_feed.py +++ b/nikola/plugins/command/import_feed.py @@ -43,6 +43,7 @@ from nikola.plugin_categories import Command from nikola import utils from nikola.utils import req_missing from nikola.plugins.basic_import import ImportMixin +from nikola.plugins.command.init import SAMPLE_CONF, prepare_config LOGGER = utils.get_logger('import_feed', utils.STDERR_HANDLER) @@ -82,7 +83,7 @@ class CommandImportFeed(Command, ImportMixin): self.import_posts(channel) self.write_configuration(self.get_configuration_output_path( - ), conf_template.render(**self.context)) + ), conf_template.render(**prepare_config(self.context))) @classmethod def get_channel_from_file(cls, filename): @@ -90,7 +91,7 @@ class CommandImportFeed(Command, ImportMixin): @staticmethod def populate_context(channel): - context = {} + context = SAMPLE_CONF.copy() context['DEFAULT_LANG'] = channel.feed.title_detail.language \ if channel.feed.title_detail.language else 'en' context['BLOG_TITLE'] = channel.feed.title @@ -100,9 +101,11 @@ class CommandImportFeed(Command, ImportMixin): context['BLOG_EMAIL'] = channel.feed.author_detail.get('email', '') if 'author_detail' in channel.feed else '' context['BLOG_AUTHOR'] = channel.feed.author_detail.get('name', '') if 'author_detail' in channel.feed else '' - context['POST_PAGES'] = '''( - ("posts/*.html", "posts", "post.tmpl", True), - ("stories/*.html", "stories", "story.tmpl", False), + context['POSTS'] = '''( + ("posts/*.html", "posts", "post.tmpl"), + )''' + context['PAGES'] = '''( + ("stories/*.html", "stories", "story.tmpl"), )''' context['COMPILERS'] = '''{ "rest": ('.txt', '.rst'), diff --git a/nikola/plugins/command/import_wordpress.py b/nikola/plugins/command/import_wordpress.py index 0c9915a..b567c77 100644 --- a/nikola/plugins/command/import_wordpress.py +++ b/nikola/plugins/command/import_wordpress.py @@ -50,6 +50,8 @@ from nikola.plugin_categories import Command from nikola import utils from nikola.utils import req_missing from nikola.plugins.basic_import import ImportMixin, links +from nikola.nikola import DEFAULT_TRANSLATIONS_PATTERN +from nikola.plugins.command.init import SAMPLE_CONF, prepare_config LOGGER = utils.get_logger('import_wordpress', utils.STDERR_HANDLER) @@ -84,6 +86,23 @@ class CommandImportWordpress(Command, ImportMixin): 'type': bool, 'help': "Do not try to download files for the import", }, + { + 'name': 'separate_qtranslate_content', + 'long': 'qtranslate', + 'default': False, + 'type': bool, + 'help': "Look for translations generated by qtranslate plugin", + # WARNING: won't recover translated titles that actually + # don't seem to be part of the wordpress XML export at the + # time of writing :( + }, + { + 'name': 'translations_pattern', + 'long': 'translations_pattern', + 'default': None, + 'type': str, + 'help': "The pattern for translation files names", + }, ] def _execute(self, options={}, args=[]): @@ -114,6 +133,9 @@ class CommandImportWordpress(Command, ImportMixin): self.exclude_drafts = options.get('exclude_drafts', False) self.no_downloads = options.get('no_downloads', False) + self.separate_qtranslate_content = options.get('separate_qtranslate_content') + self.translations_pattern = options.get('translations_pattern') + if not self.no_downloads: def show_info_about_mising_module(modulename): LOGGER.error( @@ -135,15 +157,21 @@ class CommandImportWordpress(Command, ImportMixin): self.context = self.populate_context(channel) conf_template = self.generate_base_site() + # If user has specified a custom pattern for translation files we + # need to fix the config + if self.translations_pattern: + self.context['TRANSLATIONS_PATTERN'] = self.translations_pattern + self.import_posts(channel) self.context['REDIRECTIONS'] = self.configure_redirections( self.url_map) self.write_urlmap_csv( os.path.join(self.output_folder, 'url_map.csv'), self.url_map) - rendered_template = conf_template.render(**self.context) + rendered_template = conf_template.render(**prepare_config(self.context)) rendered_template = re.sub('# REDIRECTIONS = ', 'REDIRECTIONS = ', rendered_template) + if self.timezone: rendered_template = re.sub('# TIMEZONE = \'UTC\'', 'TIMEZONE = \'' + self.timezone + '\'', @@ -194,8 +222,9 @@ class CommandImportWordpress(Command, ImportMixin): def populate_context(channel): wordpress_namespace = channel.nsmap['wp'] - context = {} + context = SAMPLE_CONF.copy() context['DEFAULT_LANG'] = get_text_tag(channel, 'language', 'en')[:2] + context['TRANSLATIONS_PATTERN'] = DEFAULT_TRANSLATIONS_PATTERN context['BLOG_TITLE'] = get_text_tag(channel, 'title', 'PUT TITLE HERE') context['BLOG_DESCRIPTION'] = get_text_tag( @@ -205,9 +234,10 @@ class CommandImportWordpress(Command, ImportMixin): base_site_url = channel.find('{{{0}}}author'.format(wordpress_namespace)) context['BASE_URL'] = get_text_tag(base_site_url, None, - "http://foo.com") + "http://foo.com/") + if not context['BASE_URL'].endswith('/'): + context['BASE_URL'] += '/' context['SITE_URL'] = context['BASE_URL'] - context['THEME'] = 'bootstrap3' author = channel.find('{{{0}}}author'.format(wordpress_namespace)) context['BLOG_EMAIL'] = get_text_tag( @@ -253,7 +283,7 @@ class CommandImportWordpress(Command, ImportMixin): + list(path.split('/')))) dst_dir = os.path.dirname(dst_path) utils.makedirs(dst_dir) - LOGGER.notice("Downloading {0} => {1}".format(url, dst_path)) + LOGGER.info("Downloading {0} => {1}".format(url, dst_path)) self.download_url_content_to_file(url, dst_path) dst_url = '/'.join(dst_path.split(os.sep)[2:]) links[link] = '/' + dst_url @@ -288,7 +318,7 @@ class CommandImportWordpress(Command, ImportMixin): # your blogging into another site or system its not. # Why don't they just use JSON? if sys.version_info[0] == 2: - metadata = phpserialize.loads(meta_value.text) + metadata = phpserialize.loads(utils.sys_encode(meta_value.text)) size_key = 'sizes' file_key = 'file' else: @@ -307,7 +337,7 @@ class CommandImportWordpress(Command, ImportMixin): + list(path.split('/')))) dst_dir = os.path.dirname(dst_path) utils.makedirs(dst_dir) - LOGGER.notice("Downloading {0} => {1}".format(url, dst_path)) + LOGGER.info("Downloading {0} => {1}".format(url, dst_path)) self.download_url_content_to_file(url, dst_path) dst_url = '/'.join(dst_path.split(os.sep)[2:]) links[url] = '/' + dst_url @@ -350,14 +380,17 @@ class CommandImportWordpress(Command, ImportMixin): # link is something like http://foo.com/2012/09/01/hello-world/ # So, take the path, utils.slugify it, and that's our slug link = get_text_tag(item, 'link', None) - path = unquote(urlparse(link).path) + path = unquote(urlparse(link).path.strip('/')) # In python 2, path is a str. slug requires a unicode # object. According to wikipedia, unquoted strings will # usually be UTF8 if isinstance(path, utils.bytes_str): path = path.decode('utf8') - slug = utils.slugify(path) + pathlist = path.split('/') + if len(pathlist) > 1: + out_folder = os.path.join(*([out_folder] + pathlist[:-1])) + slug = utils.slugify(pathlist[-1]) if not slug: # it happens if the post has no "nice" URL slug = get_text_tag( item, '{{{0}}}post_name'.format(wordpress_namespace), None) @@ -395,21 +428,43 @@ class CommandImportWordpress(Command, ImportMixin): continue tags.append(text) + if '$latex' in content: + tags.append('mathjax') + if is_draft and self.exclude_drafts: LOGGER.notice('Draft "{0}" will not be imported.'.format(title)) elif content.strip(): # If no content is found, no files are written. - self.url_map[link] = self.context['SITE_URL'] + '/' + \ - out_folder + '/' + slug + '.html' - - content = self.transform_content(content) - - self.write_metadata(os.path.join(self.output_folder, out_folder, - slug + '.meta'), - title, slug, post_date, description, tags) - self.write_content( - os.path.join(self.output_folder, out_folder, slug + '.wp'), - content) + self.url_map[link] = (self.context['SITE_URL'] + out_folder + '/' + + slug + '.html') + if hasattr(self, "separate_qtranslate_content") \ + and self.separate_qtranslate_content: + content_translations = separate_qtranslate_content(content) + else: + content_translations = {"": content} + default_language = self.context["DEFAULT_LANG"] + for lang, content in content_translations.items(): + if lang: + out_meta_filename = slug + '.meta' + if lang == default_language: + out_content_filename = slug + '.wp' + else: + out_content_filename \ + = utils.get_translation_candidate(self.context, + slug + ".wp", lang) + meta_slug = slug + else: + out_meta_filename = slug + '.meta' + out_content_filename = slug + '.wp' + meta_slug = slug + content = self.transform_content(content) + self.write_metadata(os.path.join(self.output_folder, out_folder, + out_meta_filename), + title, meta_slug, post_date, description, tags) + self.write_content( + os.path.join(self.output_folder, + out_folder, out_content_filename), + content) else: LOGGER.warn('Not going to import "{0}" because it seems to contain' ' no content.'.format(title)) @@ -441,3 +496,47 @@ def get_text_tag(tag, name, default): return t.text else: return default + + +def separate_qtranslate_content(text): + """Parse the content of a wordpress post or page and separate + the various language specific contents when they are delimited + with qtranslate tags: <!--:LL-->blabla<!--:-->""" + # TODO: uniformize qtranslate tags <!--/en--> => <!--:--> + qt_start = "<!--:" + qt_end = "-->" + qt_end_with_lang_len = 5 + qt_chunks = text.split(qt_start) + content_by_lang = {} + common_txt_list = [] + for c in qt_chunks: + if not c.strip(): + continue + if c.startswith(qt_end): + # just after the end of a language specific section, there may + # be some piece of common text or tags, or just nothing + lang = "" # default language + c = c.lstrip(qt_end) + if not c: + continue + elif c[2:].startswith(qt_end): + # a language specific section (with language code at the begining) + lang = c[:2] + c = c[qt_end_with_lang_len:] + else: + # nowhere specific (maybe there is no language section in the + # currently parsed content) + lang = "" # default language + if not lang: + common_txt_list.append(c) + for l in content_by_lang.keys(): + content_by_lang[l].append(c) + else: + content_by_lang[lang] = content_by_lang.get(lang, common_txt_list) + [c] + # in case there was no language specific section, just add the text + if common_txt_list and not content_by_lang: + content_by_lang[""] = common_txt_list + # Format back the list to simple text + for l in content_by_lang.keys(): + content_by_lang[l] = " ".join(content_by_lang[l]) + return content_by_lang diff --git a/nikola/plugins/command/init.py b/nikola/plugins/command/init.py index 96caad8..d7eeed7 100644 --- a/nikola/plugins/command/init.py +++ b/nikola/plugins/command/init.py @@ -28,18 +28,71 @@ from __future__ import print_function import os import shutil import codecs +import json from mako.template import Template import nikola +from nikola.nikola import DEFAULT_TRANSLATIONS_PATTERN from nikola.plugin_categories import Command from nikola.utils import get_logger, makedirs, STDERR_HANDLER from nikola.winutils import fix_git_symlinked LOGGER = get_logger('init', STDERR_HANDLER) +SAMPLE_CONF = { + 'BLOG_AUTHOR': "Your Name", + 'BLOG_TITLE': "Demo Site", + 'SITE_URL': "http://getnikola.com/", + 'BLOG_EMAIL': "joe@demo.site", + 'BLOG_DESCRIPTION': "This is a demo site for Nikola.", + 'DEFAULT_LANG': "en", + 'THEME': 'bootstrap3', + 'COMMENT_SYSTEM': 'disqus', + 'COMMENT_SYSTEM_ID': 'nikolademo', + 'TRANSLATIONS_PATTERN': DEFAULT_TRANSLATIONS_PATTERN, + 'POSTS': """( +("posts/*.rst", "posts", "post.tmpl"), +("posts/*.txt", "posts", "post.tmpl"), +)""", + 'PAGES': """( +("stories/*.rst", "stories", "story.tmpl"), +("stories/*.txt", "stories", "story.tmpl"), +)""", + 'COMPILERS': """{ +"rest": ('.rst', '.txt'), +"markdown": ('.md', '.mdown', '.markdown'), +"textile": ('.textile',), +"txt2tags": ('.t2t',), +"bbcode": ('.bb',), +"wiki": ('.wiki',), +"ipynb": ('.ipynb',), +"html": ('.html', '.htm'), +# PHP files are rendered the usual way (i.e. with the full templates). +# The resulting files have .php extensions, making it possible to run +# them without reconfiguring your server to recognize them. +"php": ('.php',), +# Pandoc detects the input from the source filename +# but is disabled by default as it would conflict +# with many of the others. +# "pandoc": ('.rst', '.md', '.txt'), +}""", + 'REDIRECTIONS': [], +} + + +# In order to ensure proper escaping, all variables but the three +# pre-formatted ones are handled by json.dumps(). +def prepare_config(config): + """Parse sample config with JSON.""" + p = config.copy() + p.update(dict((k, json.dumps(v)) for k, v in p.items() + if k not in ('POSTS', 'PAGES', 'COMPILERS'))) + return p + class CommandInit(Command): + """Create a new site.""" name = "init" @@ -57,40 +110,6 @@ class CommandInit(Command): } ] - SAMPLE_CONF = { - 'BLOG_AUTHOR': "Your Name", - 'BLOG_TITLE': "Demo Site", - 'SITE_URL': "http://getnikola.com/", - 'BLOG_EMAIL': "joe@demo.site", - 'BLOG_DESCRIPTION': "This is a demo site for Nikola.", - 'DEFAULT_LANG': "en", - 'THEME': 'bootstrap3', - - 'POSTS': """( - ("posts/*.rst", "posts", "post.tmpl"), - ("posts/*.txt", "posts", "post.tmpl"), -)""", - 'PAGES': """( - ("stories/*.rst", "stories", "story.tmpl"), - ("stories/*.txt", "stories", "story.tmpl"), -)""", - 'COMPILERS': """{ - "rest": ('.rst', '.txt'), - "markdown": ('.md', '.mdown', '.markdown'), - "textile": ('.textile',), - "txt2tags": ('.t2t',), - "bbcode": ('.bb',), - "wiki": ('.wiki',), - "ipynb": ('.ipynb',), - "html": ('.html', '.htm'), - # Pandoc detects the input from the source filename - # but is disabled by default as it would conflict - # with many of the others. - # "pandoc": ('.rst', '.md', '.txt'), -}""", - 'REDIRECTIONS': '[]', - } - @classmethod def copy_sample_site(cls, target): lib_path = cls.get_path_to_nikola_modules() @@ -105,7 +124,7 @@ class CommandInit(Command): conf_template = Template(filename=template_path) conf_path = os.path.join(target, 'conf.py') with codecs.open(conf_path, 'w+', 'utf8') as fd: - fd.write(conf_template.render(**cls.SAMPLE_CONF)) + fd.write(conf_template.render(**prepare_config(SAMPLE_CONF))) @classmethod def create_empty_site(cls, target): @@ -122,16 +141,13 @@ class CommandInit(Command): print("Usage: nikola init folder [options]") return False target = args[0] - if target is None: - print(self.usage) + if not options or not options.get('demo'): + self.create_empty_site(target) + LOGGER.info('Created empty site at {0}.'.format(target)) else: - if not options or not options.get('demo'): - self.create_empty_site(target) - LOGGER.notice('Created empty site at {0}.'.format(target)) - else: - self.copy_sample_site(target) - LOGGER.notice("A new site with example data has been created at " - "{0}.".format(target)) - LOGGER.notice("See README.txt in that folder for more information.") - - self.create_configuration(target) + self.copy_sample_site(target) + LOGGER.info("A new site with example data has been created at " + "{0}.".format(target)) + LOGGER.info("See README.txt in that folder for more information.") + + self.create_configuration(target) diff --git a/nikola/plugins/command/install_plugin.py b/nikola/plugins/command/install_plugin.py index 1d6584d..34223c0 100644 --- a/nikola/plugins/command/install_plugin.py +++ b/nikola/plugins/command/install_plugin.py @@ -27,7 +27,6 @@ from __future__ import print_function import codecs import os -import sys import json import shutil import subprocess @@ -123,10 +122,10 @@ class CommandInstallPlugin(Command): def do_install(self, name, data): if name in data: utils.makedirs(self.output_dir) - LOGGER.notice('Downloading: ' + data[name]) + LOGGER.info('Downloading: ' + data[name]) zip_file = BytesIO() zip_file.write(requests.get(data[name]).content) - LOGGER.notice('Extracting: {0} into plugins'.format(name)) + LOGGER.info('Extracting: {0} into plugins'.format(name)) utils.extract_all(zip_file, 'plugins') dest_path = os.path.join('plugins', name) else: @@ -142,13 +141,13 @@ class CommandInstallPlugin(Command): LOGGER.error("{0} is already installed".format(name)) return False - LOGGER.notice('Copying {0} into plugins'.format(plugin_path)) + LOGGER.info('Copying {0} into plugins'.format(plugin_path)) shutil.copytree(plugin_path, dest_path) reqpath = os.path.join(dest_path, 'requirements.txt') if os.path.exists(reqpath): LOGGER.notice('This plugin has Python dependencies.') - LOGGER.notice('Installing dependencies with pip...') + LOGGER.info('Installing dependencies with pip...') try: subprocess.check_call(('pip', 'install', '-r', reqpath)) except subprocess.CalledProcessError: @@ -159,7 +158,7 @@ class CommandInstallPlugin(Command): print('You have to install those yourself or through a ' 'package manager.') else: - LOGGER.notice('Dependency installation succeeded.') + LOGGER.info('Dependency installation succeeded.') reqnpypath = os.path.join(dest_path, 'requirements-nonpy.txt') if os.path.exists(reqnpypath): LOGGER.notice('This plugin has third-party ' @@ -177,10 +176,10 @@ class CommandInstallPlugin(Command): 'manager.') confpypath = os.path.join(dest_path, 'conf.py.sample') if os.path.exists(confpypath): - LOGGER.notice('This plugin has a sample config file.') + LOGGER.notice('This plugin has a sample config file. Integrate it with yours in order to make this plugin work!') print('Contents of the conf.py.sample file:\n') with codecs.open(confpypath, 'rb', 'utf-8') as fh: - if sys.platform == 'win32': + if self.site.colorful: print(indent(pygments.highlight( fh.read(), PythonLexer(), TerminalFormatter()), 4 * ' ')) diff --git a/nikola/plugins/command/install_theme.py b/nikola/plugins/command/install_theme.py index 569397b..47c73b4 100644 --- a/nikola/plugins/command/install_theme.py +++ b/nikola/plugins/command/install_theme.py @@ -26,7 +26,6 @@ from __future__ import print_function import os -import sys import codecs import json import shutil @@ -137,10 +136,10 @@ class CommandInstallTheme(Command): def do_install(self, name, data): if name in data: utils.makedirs(self.output_dir) - LOGGER.notice('Downloading: ' + data[name]) + LOGGER.info('Downloading: ' + data[name]) zip_file = BytesIO() zip_file.write(requests.get(data[name]).content) - LOGGER.notice('Extracting: {0} into themes'.format(name)) + LOGGER.info('Extracting: {0} into themes'.format(name)) utils.extract_all(zip_file) dest_path = os.path.join('themes', name) else: @@ -156,14 +155,14 @@ class CommandInstallTheme(Command): LOGGER.error("{0} is already installed".format(name)) return False - LOGGER.notice('Copying {0} into themes'.format(theme_path)) + LOGGER.info('Copying {0} into themes'.format(theme_path)) shutil.copytree(theme_path, dest_path) confpypath = os.path.join(dest_path, 'conf.py.sample') if os.path.exists(confpypath): - LOGGER.notice('This plugin has a sample config file. Integrate it with yours in order to make this theme work!') + LOGGER.notice('This theme has a sample config file. Integrate it with yours in order to make this theme work!') print('Contents of the conf.py.sample file:\n') with codecs.open(confpypath, 'rb', 'utf-8') as fh: - if sys.platform == 'win32': + if self.site.colorful: print(indent(pygments.highlight( fh.read(), PythonLexer(), TerminalFormatter()), 4 * ' ')) diff --git a/nikola/plugins/command/new_page.plugin b/nikola/plugins/command/new_page.plugin new file mode 100644 index 0000000..1f1c84c --- /dev/null +++ b/nikola/plugins/command/new_page.plugin @@ -0,0 +1,9 @@ +[Core] +Name = new_page +Module = new_page + +[Documentation] +Author = Roberto Alsina, Chris Warrick +Version = 0.1 +Website = http://getnikola.com +Description = Create a new page. diff --git a/nikola/plugins/command/new_page.py b/nikola/plugins/command/new_page.py new file mode 100644 index 0000000..39c0c1d --- /dev/null +++ b/nikola/plugins/command/new_page.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2014 Roberto Alsina, Chris Warrick and others. + +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the +# Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from __future__ import unicode_literals, print_function + +from nikola.plugin_categories import Command + + +class CommandNewPage(Command): + """Create a new page.""" + + name = "new_page" + doc_usage = "[options] [path]" + doc_purpose = "create a new page in the site" + cmd_options = [ + { + 'name': 'title', + 'short': 't', + 'long': 'title', + 'type': str, + 'default': '', + 'help': 'Title for the page.' + }, + { + 'name': 'onefile', + 'short': '1', + 'type': bool, + 'default': False, + 'help': 'Create the page with embedded metadata (single file format)' + }, + { + 'name': 'twofile', + 'short': '2', + 'type': bool, + 'default': False, + 'help': 'Create the page with separate metadata (two file format)' + }, + { + 'name': 'content_format', + 'short': 'f', + 'long': 'format', + 'type': str, + 'default': '', + 'help': 'Markup format for the page, one of rest, markdown, wiki, ' + 'bbcode, html, textile, txt2tags', + }, + ] + + def _execute(self, options, args): + """Create a new page.""" + options['tags'] = '' + options['schedule'] = False + options['is_page'] = True + # Even though stuff was split into `new_page`, it’s easier to do it + # there not to duplicate the code. + p = self.site.plugin_manager.getPluginByName('new_post', 'Command').plugin_object + return p.execute(options, args) diff --git a/nikola/plugins/command/new_post.py b/nikola/plugins/command/new_post.py index a5c551d..cd37a75 100644 --- a/nikola/plugins/command/new_post.py +++ b/nikola/plugins/command/new_post.py @@ -35,7 +35,9 @@ from blinker import signal from nikola.plugin_categories import Command from nikola import utils -LOGGER = utils.get_logger('new_post', utils.STDERR_HANDLER) +POSTLOGGER = utils.get_logger('new_post', utils.STDERR_HANDLER) +PAGELOGGER = utils.get_logger('new_page', utils.STDERR_HANDLER) +LOGGER = POSTLOGGER def filter_post_pages(compiler, is_post, compilers, post_pages): @@ -57,8 +59,8 @@ def filter_post_pages(compiler, is_post, compilers, post_pages): type_name = "post" if is_post else "page" raise Exception("Can't find a way, using your configuration, to create " "a {0} in format {1}. You may want to tweak " - "COMPILERS or POSTS/PAGES in conf.py".format( - type_name, compiler)) + "COMPILERS or {2}S in conf.py".format( + type_name, compiler, type_name.upper())) return filtered[0] @@ -134,7 +136,7 @@ class CommandNewPost(Command): 'long': 'page', 'type': bool, 'default': False, - 'help': 'Create a page instead of a blog post.' + 'help': 'Create a page instead of a blog post. (see also: `nikola new_page`)' }, { 'name': 'title', @@ -142,36 +144,36 @@ class CommandNewPost(Command): 'long': 'title', 'type': str, 'default': '', - 'help': 'Title for the page/post.' + 'help': 'Title for the post.' }, { 'name': 'tags', 'long': 'tags', 'type': str, 'default': '', - 'help': 'Comma-separated tags for the page/post.' + 'help': 'Comma-separated tags for the post.' }, { 'name': 'onefile', 'short': '1', 'type': bool, 'default': False, - 'help': 'Create post with embedded metadata (single file format)' + 'help': 'Create the post with embedded metadata (single file format)' }, { 'name': 'twofile', 'short': '2', 'type': bool, 'default': False, - 'help': 'Create post with separate metadata (two file format)' + 'help': 'Create the post with separate metadata (two file format)' }, { - 'name': 'post_format', + 'name': 'content_format', 'short': 'f', 'long': 'format', 'type': str, 'default': '', - 'help': 'Markup format for post, one of rest, markdown, wiki, ' + 'help': 'Markup format for the post, one of rest, markdown, wiki, ' 'bbcode, html, textile, txt2tags', }, { @@ -179,13 +181,14 @@ class CommandNewPost(Command): 'short': 's', 'type': bool, 'default': False, - 'help': 'Schedule post based on recurrence rule' + 'help': 'Schedule the post based on recurrence rule' }, ] def _execute(self, options, args): """Create a new post or page.""" + global LOGGER compiler_names = [p.name for p in self.site.plugin_manager.getPluginsOfCategory( "PageCompiler")] @@ -198,38 +201,46 @@ class CommandNewPost(Command): else: path = None + # Even though stuff was split into `new_page`, it’s easier to do it + # here not to duplicate the code. is_page = options.get('is_page', False) is_post = not is_page + content_type = 'page' if is_page else 'post' title = options['title'] or None tags = options['tags'] onefile = options['onefile'] twofile = options['twofile'] + if is_page: + LOGGER = PAGELOGGER + else: + LOGGER = POSTLOGGER + if twofile: onefile = False if not onefile and not twofile: onefile = self.site.config.get('ONE_FILE_POSTS', True) - post_format = options['post_format'] + content_format = options['content_format'] - if not post_format: # Issue #400 - post_format = get_default_compiler( + if not content_format: # Issue #400 + content_format = get_default_compiler( is_post, self.site.config['COMPILERS'], self.site.config['post_pages']) - if post_format not in compiler_names: - LOGGER.error("Unknown post format " + post_format) + if content_format not in compiler_names: + LOGGER.error("Unknown {0} format {1}".format(content_type, content_format)) return compiler_plugin = self.site.plugin_manager.getPluginByName( - post_format, "PageCompiler").plugin_object + content_format, "PageCompiler").plugin_object # Guess where we should put this - entry = filter_post_pages(post_format, is_post, + entry = filter_post_pages(content_format, is_post, self.site.config['COMPILERS'], self.site.config['post_pages']) - print("Creating New Post") + print("Creating New {0}".format(content_type.title())) print("-----------------\n") if title is None: print("Enter title: ", end='') @@ -247,7 +258,7 @@ class CommandNewPost(Command): if isinstance(path, utils.bytes_str): path = path.decode(sys.stdin.encoding) slug = utils.slugify(os.path.splitext(os.path.basename(path))[0]) - # Calculate the date to use for the post + # Calculate the date to use for the content schedule = options['schedule'] or self.site.config['SCHEDULE_ALL'] rule = self.site.config['SCHEDULE_RULE'] force_today = self.site.config['SCHEDULE_FORCE_TODAY'] @@ -275,7 +286,7 @@ class CommandNewPost(Command): metadata = self.site.config['ADDITIONAL_METADATA'] compiler_plugin.create_post( txt_path, onefile, title=title, - slug=slug, date=date, tags=tags, **metadata) + slug=slug, date=date, tags=tags, is_page=is_page, **metadata) event = dict(path=txt_path) @@ -283,9 +294,9 @@ class CommandNewPost(Command): with codecs.open(meta_path, "wb+", "utf8") as fd: fd.write('\n'.join(data)) with codecs.open(txt_path, "wb+", "utf8") as fd: - fd.write("Write your post here.") - LOGGER.notice("Your post's metadata is at: {0}".format(meta_path)) + fd.write("Write your {0} here.".format(content_type)) + LOGGER.info("Your {0}'s metadata is at: {1}".format(content_type, meta_path)) event['meta_path'] = meta_path - LOGGER.notice("Your post's text is at: {0}".format(txt_path)) + LOGGER.info("Your {0}'s text is at: {1}".format(content_type, txt_path)) - signal('new_post').send(self, **event) + signal('new_' + content_type).send(self, **event) diff --git a/nikola/plugins/command/planetoid/__init__.py b/nikola/plugins/command/planetoid/__init__.py index ff5dd13..fe1a59b 100644 --- a/nikola/plugins/command/planetoid/__init__.py +++ b/nikola/plugins/command/planetoid/__init__.py @@ -162,12 +162,12 @@ class Planetoid(Command, Task): # TODO: log failure return if parsed.feed.get('title'): - LOGGER.notice(parsed.feed.title) + LOGGER.info(parsed.feed.title) else: - LOGGER.notice(feed.url) + LOGGER.info(feed.url) feed.etag = parsed.get('etag', 'foo') modified = tuple(parsed.get('date_parsed', (1970, 1, 1)))[:6] - LOGGER.notice("==========>", modified) + LOGGER.info("==========>", modified) modified = datetime.datetime(*modified) feed.last_modified = modified feed.save() @@ -176,14 +176,14 @@ class Planetoid(Command, Task): # TODO log failure return for entry_data in parsed.entries: - LOGGER.notice("=========================================") + LOGGER.info("=========================================") date = entry_data.get('published_parsed', None) if date is None: date = entry_data.get('updated_parsed', None) if date is None: LOGGER.error("Can't parse date from:\n", entry_data) return False - LOGGER.notice("DATE:===>", date) + LOGGER.info("DATE:===>", date) date = datetime.datetime(*(date[:6])) title = "%s: %s" % (feed.name, entry_data.get('title', 'Sin tÃtulo')) content = entry_data.get('content', None) @@ -195,9 +195,9 @@ class Planetoid(Command, Task): content = entry_data.get('summary', 'Sin contenido') guid = str(entry_data.get('guid', entry_data.link)) link = entry_data.link - LOGGER.notice(repr([date, title])) + LOGGER.info(repr([date, title])) e = list(Entry.select().where(Entry.guid == guid)) - LOGGER.notice( + LOGGER.info( repr(dict( date=date, title=title, diff --git a/nikola/plugins/command/serve.py b/nikola/plugins/command/serve.py index 2dd15c1..f27d1f7 100644 --- a/nikola/plugins/command/serve.py +++ b/nikola/plugins/command/serve.py @@ -26,6 +26,7 @@ from __future__ import print_function import os +import webbrowser try: from BaseHTTPServer import HTTPServer from SimpleHTTPServer import SimpleHTTPRequestHandler @@ -37,7 +38,7 @@ from nikola.plugin_categories import Command from nikola.utils import get_logger -class CommandBuild(Command): +class CommandServe(Command): """Start test server.""" name = "serve" @@ -57,11 +58,19 @@ class CommandBuild(Command): { 'name': 'address', 'short': 'a', - 'long': '--address', + 'long': 'address', 'type': str, 'default': '127.0.0.1', 'help': 'Address to bind (default: 127.0.0.1)', }, + { + 'name': 'browser', + 'short': 'b', + 'long': 'browser', + 'type': bool, + 'default': False, + 'help': 'Open the test server in a web browser', + } ) def _execute(self, options, args): @@ -75,7 +84,11 @@ class CommandBuild(Command): httpd = HTTPServer((options['address'], options['port']), OurHTTPRequestHandler) sa = httpd.socket.getsockname() - self.logger.notice("Serving HTTP on {0} port {1} ...".format(*sa)) + self.logger.info("Serving HTTP on {0} port {1} ...".format(*sa)) + if options['browser']: + server_url = "http://{0}:{1}/".format(options['address'], options['port']) + self.logger.info("Opening {0} in the default web browser ...".format(server_url)) + webbrowser.open(server_url) httpd.serve_forever() diff --git a/nikola/plugins/compile/asciidoc.py b/nikola/plugins/compile/asciidoc.py index 12cb4bf..68f96d9 100644 --- a/nikola/plugins/compile/asciidoc.py +++ b/nikola/plugins/compile/asciidoc.py @@ -40,7 +40,7 @@ from nikola.utils import makedirs, req_missing try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA class CompileAsciiDoc(PageCompiler): @@ -57,11 +57,8 @@ class CompileAsciiDoc(PageCompiler): if e.strreror == 'No such file or directory': req_missing(['asciidoc'], 'build this site (compile with asciidoc)', python=False) - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) makedirs(os.path.dirname(path)) @@ -71,4 +68,4 @@ class CompileAsciiDoc(PageCompiler): for k, v in metadata.items(): fd.write('.. {0}: {1}\n'.format(k, v)) fd.write("/////////////////////////////////////////////\n") - fd.write("\nWrite your post here.") + fd.write("\nWrite your {0} here.".format('page' if is_page else 'post')) diff --git a/nikola/plugins/compile/bbcode.py b/nikola/plugins/compile/bbcode.py index 5345be3..0961ffe 100644 --- a/nikola/plugins/compile/bbcode.py +++ b/nikola/plugins/compile/bbcode.py @@ -40,7 +40,7 @@ from nikola.utils import makedirs, req_missing try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA class CompileBbcode(PageCompiler): @@ -66,11 +66,8 @@ class CompileBbcode(PageCompiler): output = self.parser.format(data) out_file.write(output) - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) makedirs(os.path.dirname(path)) @@ -80,4 +77,4 @@ class CompileBbcode(PageCompiler): for k, v in metadata.items(): fd.write('.. {0}: {1}\n'.format(k, v)) fd.write('-->[/note]\n\n') - fd.write("Write your post here.") + fd.write("Write your {0} here.".format('page' if is_page else 'post')) diff --git a/nikola/plugins/compile/html.py b/nikola/plugins/compile/html.py index 5352f00..09a9756 100644 --- a/nikola/plugins/compile/html.py +++ b/nikola/plugins/compile/html.py @@ -36,7 +36,7 @@ from nikola.utils import makedirs try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA _META_SEPARATOR = '(' + os.linesep * 2 + '|' + ('\n' * 2) + '|' + ("\r\n" * 2) + ')' @@ -56,11 +56,8 @@ class CompileHtml(PageCompiler): out_file.write(data) return True - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) makedirs(os.path.dirname(path)) @@ -70,4 +67,4 @@ class CompileHtml(PageCompiler): for k, v in metadata.items(): fd.write('.. {0}: {1}\n'.format(k, v)) fd.write('-->\n\n') - fd.write("\n<p>Write your post here.</p>\n") + fd.write("\n<p>Write your {0} here.</p>\n".format('page' if is_page else 'post')) diff --git a/nikola/plugins/compile/ipynb/__init__.py b/nikola/plugins/compile/ipynb/__init__.py index 5f2f0b3..2b1fd28 100644 --- a/nikola/plugins/compile/ipynb/__init__.py +++ b/nikola/plugins/compile/ipynb/__init__.py @@ -44,7 +44,7 @@ from nikola.utils import makedirs, req_missing try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA class CompileIPynb(PageCompiler): @@ -66,11 +66,8 @@ class CompileIPynb(PageCompiler): (body, resources) = exportHtml.from_notebook_node(nb_json) out_file.write(body) - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) d_name = os.path.dirname(path) @@ -81,7 +78,7 @@ class CompileIPynb(PageCompiler): metadata['date'], metadata['tags'], metadata['link'], metadata['description'], metadata['type']))) - print("Your post's metadata is at: ", meta_path) + print("Your {0}'s metadata is at: {1}".format('page' if is_page else 'post', meta_path)) with codecs.open(path, "wb+", "utf8") as fd: fd.write("""{ "metadata": { diff --git a/nikola/plugins/compile/markdown/__init__.py b/nikola/plugins/compile/markdown/__init__.py index 1376b11..d0fa66a 100644 --- a/nikola/plugins/compile/markdown/__init__.py +++ b/nikola/plugins/compile/markdown/__init__.py @@ -54,7 +54,7 @@ except ImportError: try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA from nikola.plugin_categories import PageCompiler from nikola.utils import makedirs, req_missing @@ -81,11 +81,8 @@ class CompileMarkdown(PageCompiler): output = markdown(data, self.extensions) out_file.write(output) - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) makedirs(os.path.dirname(path)) @@ -95,4 +92,4 @@ class CompileMarkdown(PageCompiler): for k, v in metadata.items(): fd.write('.. {0}: {1}\n'.format(k, v)) fd.write('-->\n\n') - fd.write("Write your post here.") + fd.write("Write your {0} here.".format('page' if is_page else 'post')) diff --git a/nikola/plugins/compile/misaka.py b/nikola/plugins/compile/misaka.py index 8777ffc..4951c9f 100644 --- a/nikola/plugins/compile/misaka.py +++ b/nikola/plugins/compile/misaka.py @@ -40,7 +40,7 @@ except ImportError: try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA gist_extension = None podcast_extension = None @@ -73,11 +73,8 @@ class CompileMisaka(PageCompiler): output = misaka.html(data, extensions=self.ext) out_file.write(output) - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) makedirs(os.path.dirname(path)) @@ -87,4 +84,4 @@ class CompileMisaka(PageCompiler): for k, v in metadata.items(): fd.write('.. {0}: {1}\n'.format(k, v)) fd.write('-->\n\n') - fd.write("\nWrite your post here.") + fd.write("\nWrite your {0} here.".format('page' if is_page else 'post')) diff --git a/nikola/plugins/compile/pandoc.py b/nikola/plugins/compile/pandoc.py index 57c7d71..654c7c8 100644 --- a/nikola/plugins/compile/pandoc.py +++ b/nikola/plugins/compile/pandoc.py @@ -40,7 +40,7 @@ from nikola.utils import req_missing, makedirs try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA class CompilePandoc(PageCompiler): @@ -56,11 +56,8 @@ class CompilePandoc(PageCompiler): if e.strreror == 'No such file or directory': req_missing(['pandoc'], 'build this site (compile with pandoc)', python=False) - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) makedirs(os.path.dirname(path)) @@ -70,4 +67,4 @@ class CompilePandoc(PageCompiler): for k, v in metadata.items(): fd.write('.. {0}: {1}\n'.format(k, v)) fd.write('-->\n\n') - fd.write("Write your post here.") + fd.write("Write your {0} here.".format('page' if is_page else 'post')) diff --git a/nikola/plugins/compile/php.py b/nikola/plugins/compile/php.py index 14b80e8..0a652a6 100644 --- a/nikola/plugins/compile/php.py +++ b/nikola/plugins/compile/php.py @@ -38,7 +38,7 @@ from nikola.utils import makedirs try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA class CompilePhp(PageCompiler): @@ -50,11 +50,8 @@ class CompilePhp(PageCompiler): makedirs(os.path.dirname(dest)) shutil.copyfile(source, dest) - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) os.makedirs(os.path.dirname(path)) @@ -64,7 +61,7 @@ class CompilePhp(PageCompiler): for k, v in metadata.items(): fd.write('.. {0}: {1}\n'.format(k, v)) fd.write('-->\n\n') - fd.write("\n<p>Write your post here.</p>") + fd.write("\n<p>Write your {0} here.</p>".format('page' if is_page else 'post')) def extension(self): return ".php" diff --git a/nikola/plugins/compile/rest/__init__.py b/nikola/plugins/compile/rest/__init__.py index 50b37cf..9a4e19b 100644 --- a/nikola/plugins/compile/rest/__init__.py +++ b/nikola/plugins/compile/rest/__init__.py @@ -43,7 +43,7 @@ except ImportError: try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA from nikola.plugin_categories import PageCompiler from nikola.utils import get_logger, makedirs, req_missing @@ -102,11 +102,8 @@ class CompileRest(PageCompiler): else: return False - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) makedirs(os.path.dirname(path)) @@ -114,7 +111,7 @@ class CompileRest(PageCompiler): if onefile: for k, v in metadata.items(): fd.write('.. {0}: {1}\n'.format(k, v)) - fd.write("\nWrite your post here.") + fd.write("\nWrite your {0} here.".format('page' if is_page else 'post')) def set_site(self, site): for plugin_info in site.plugin_manager.getPluginsOfCategory("RestExtension"): @@ -174,6 +171,38 @@ class NikolaReader(docutils.readers.standalone.Reader): def add_node(node, visit_function=None, depart_function=None): + """ + Register a Docutils node class. + This function is completely optional. It is a same concept as + `Sphinx add_node function <http://sphinx-doc.org/ext/appapi.html#sphinx.application.Sphinx.add_node>`_. + + For example:: + + class Plugin(RestExtension): + + name = "rest_math" + + def set_site(self, site): + self.site = site + directives.register_directive('math', MathDirective) + add_node(MathBlock, visit_Math, depart_Math) + return super(Plugin, self).set_site(site) + + class MathDirective(Directive): + def run(self): + node = MathBlock() + return [node] + + class Math(docutils.nodes.Element): pass + + def visit_Math(self, node): + self.body.append(self.starttag(node, 'math')) + + def depart_Math(self, node): + self.body.append('</math>') + + For full example, you can refer to `Microdata plugin <http://plugins.getnikola.com/#microdata>`_ + """ docutils.nodes._add_node_class_names([node.__name__]) if visit_function: setattr(docutils.writers.html4css1.HTMLTranslator, 'visit_' + node.__name__, visit_function) diff --git a/nikola/plugins/compile/rest/listing.py b/nikola/plugins/compile/rest/listing.py index ecf885f..d70e02d 100644 --- a/nikola/plugins/compile/rest/listing.py +++ b/nikola/plugins/compile/rest/listing.py @@ -56,6 +56,18 @@ except ImportError: # docutils < 0.9 (Debian Sid For The Loss) from nikola.plugin_categories import RestExtension +# Add sphinx compatibility option +CodeBlock.option_spec['linenos'] = directives.unchanged + + +class FlexibleCodeBlock(CodeBlock): + + def run(self): + if 'linenos' in self.options: + self.options['number-lines'] = self.options['linenos'] + return super(FlexibleCodeBlock, self).run() +CodeBlock = FlexibleCodeBlock + class Plugin(RestExtension): @@ -71,6 +83,10 @@ class Plugin(RestExtension): directives.register_directive('listing', Listing) return super(Plugin, self).set_site(site) +# Add sphinx compatibility option +listing_spec = Include.option_spec +listing_spec['linenos'] = directives.unchanged + class Listing(Include): """ listing directive: create a highlighted block of code from a file in listings/ @@ -84,6 +100,7 @@ class Listing(Include): has_content = False required_arguments = 1 optional_arguments = 1 + option_spec = listing_spec def run(self): fname = self.arguments.pop(0) @@ -91,6 +108,8 @@ class Listing(Include): fpath = os.path.join('listings', fname) self.arguments.insert(0, fpath) self.options['code'] = lang + if 'linenos' in self.options: + self.options['number-lines'] = self.options['linenos'] with codecs_open(fpath, 'rb+', 'utf8') as fileobject: self.content = fileobject.read().splitlines() self.state.document.settings.record_dependencies.add(fpath) diff --git a/nikola/plugins/compile/textile.py b/nikola/plugins/compile/textile.py index 73f35c0..1679831 100644 --- a/nikola/plugins/compile/textile.py +++ b/nikola/plugins/compile/textile.py @@ -41,7 +41,7 @@ from nikola.utils import makedirs, req_missing try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA class CompileTextile(PageCompiler): @@ -62,11 +62,8 @@ class CompileTextile(PageCompiler): output = textile(data, head_offset=1) out_file.write(output) - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) makedirs(os.path.dirname(path)) @@ -76,4 +73,4 @@ class CompileTextile(PageCompiler): for k, v in metadata.items(): fd.write('.. {0}: {1}\n'.format(k, v)) fd.write('--></notextile>\n\n') - fd.write("\nWrite your post here.") + fd.write("\nWrite your {0} here.".format('page' if is_page else 'post')) diff --git a/nikola/plugins/compile/txt2tags.py b/nikola/plugins/compile/txt2tags.py index 8c9724e..bb6afa5 100644 --- a/nikola/plugins/compile/txt2tags.py +++ b/nikola/plugins/compile/txt2tags.py @@ -43,7 +43,7 @@ except ImportError: try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA from nikola.plugin_categories import PageCompiler from nikola.utils import makedirs, req_missing @@ -62,11 +62,8 @@ class CompileTxt2tags(PageCompiler): cmd = ["-t", "html", "--no-headers", "--outfile", dest, source] txt2tags(cmd) - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) makedirs(os.path.dirname(path)) @@ -76,4 +73,4 @@ class CompileTxt2tags(PageCompiler): for k, v in metadata.items(): fd.write('.. {0}: {1}\n'.format(k, v)) fd.write("-->\n'''\n") - fd.write("\nWrite your post here.") + fd.write("\nWrite your {0} here.".format('page' if is_page else 'post')) diff --git a/nikola/plugins/compile/wiki.py b/nikola/plugins/compile/wiki.py index 9a365fa..f4858c7 100644 --- a/nikola/plugins/compile/wiki.py +++ b/nikola/plugins/compile/wiki.py @@ -40,7 +40,7 @@ from nikola.plugin_categories import PageCompiler try: from collections import OrderedDict except ImportError: - OrderedDict = None # NOQA + OrderedDict = dict # NOQA from nikola.utils import makedirs, req_missing @@ -62,11 +62,8 @@ class CompileWiki(PageCompiler): output = HtmlEmitter(document).emit() out_file.write(output) - def create_post(self, path, onefile=False, **kw): - if OrderedDict is not None: - metadata = OrderedDict() - else: - metadata = {} + def create_post(self, path, onefile=False, is_page=False, **kw): + metadata = OrderedDict() metadata.update(self.default_metadata) metadata.update(kw) makedirs(os.path.dirname(path)) @@ -75,4 +72,4 @@ class CompileWiki(PageCompiler): 'one-file format is not possible, use the -2 ' 'option.') with codecs.open(path, "wb+", "utf8") as fd: - fd.write("Write your post here.") + fd.write("Write your {0} here.".format('page' if is_page else 'post')) diff --git a/nikola/plugins/loghandler/stderr.py b/nikola/plugins/loghandler/stderr.py index 75acffc..fdc892e 100644 --- a/nikola/plugins/loghandler/stderr.py +++ b/nikola/plugins/loghandler/stderr.py @@ -26,10 +26,10 @@ from nikola.plugin_categories import SignalHandler from blinker import signal -import logbook import os from nikola import DEBUG +from nikola.utils import ColorfulStderrHandler class StderrHandler(SignalHandler): @@ -40,7 +40,7 @@ class StderrHandler(SignalHandler): """Attach the handler to the logger.""" conf = self.site.config.get('LOGGING_HANDLERS').get('stderr') if conf or os.getenv('NIKOLA_DEBUG'): - self.site.loghandlers.append(logbook.StderrHandler( + self.site.loghandlers.append(ColorfulStderrHandler( level='DEBUG' if DEBUG else conf.get('loglevel', 'WARNING').upper(), format_string=u'[{record.time:%Y-%m-%dT%H:%M:%SZ}] {record.level_name}: {record.channel}: {record.message}' )) diff --git a/nikola/plugins/task/build_less.py b/nikola/plugins/task/build_less.py index 14a53f9..a672282 100644 --- a/nikola/plugins/task/build_less.py +++ b/nikola/plugins/task/build_less.py @@ -46,22 +46,34 @@ class BuildLess(Task): 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 - targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES) + 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)): @@ -82,7 +94,7 @@ class BuildLess(Task): 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, src], shell=run_in_shell) + 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)', diff --git a/nikola/plugins/task/build_sass.py b/nikola/plugins/task/build_sass.py index 7575505..becc843 100644 --- a/nikola/plugins/task/build_sass.py +++ b/nikola/plugins/task/build_sass.py @@ -47,26 +47,41 @@ class BuildSass(Task): """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 - targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES) + 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 @@ -83,7 +98,7 @@ class BuildSass(Task): 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, src], shell=run_in_shell) + 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)', diff --git a/nikola/plugins/task/bundles.py b/nikola/plugins/task/bundles.py index b035b97..fcfaf42 100644 --- a/nikola/plugins/task/bundles.py +++ b/nikola/plugins/task/bundles.py @@ -87,8 +87,8 @@ class BuildBundles(LateTask): output_path = os.path.join(kw['output_folder'], name) dname = os.path.dirname(name) file_dep = [os.path.join(kw['output_folder'], dname, fname) - for fname in files] - file_dep = filter(os.path.isfile, file_dep) # removes missing files + 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'], diff --git a/nikola/plugins/task/copy_assets.py b/nikola/plugins/task/copy_assets.py index 21f1f85..93b7fb3 100644 --- a/nikola/plugins/task/copy_assets.py +++ b/nikola/plugins/task/copy_assets.py @@ -75,8 +75,8 @@ class CopyAssets(Task): formatter = get_formatter_by_name('html', style=kw["code_color_scheme"]) utils.makedirs(os.path.dirname(code_css_path)) with codecs.open(code_css_path, 'wb+', 'utf8') as outf: - outf.write(formatter.get_style_defs('.code')) - outf.write("table.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}") + outf.write(formatter.get_style_defs(['pre.code', 'div.code pre'])) + outf.write("\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n") task = { 'basename': self.name, diff --git a/nikola/plugins/task/galleries.py b/nikola/plugins/task/galleries.py index 6977eab..880d47c 100644 --- a/nikola/plugins/task/galleries.py +++ b/nikola/plugins/task/galleries.py @@ -176,6 +176,7 @@ class Galleries(Task): thumbs = ['.thumbnail'.join(os.path.splitext(p)) for p in image_list] thumbs = [os.path.join(self.kw['output_folder'], t) for t in thumbs] + dest_img_list = [os.path.join(self.kw['output_folder'], t) for t in image_list] folders = [] @@ -193,7 +194,8 @@ class Galleries(Task): context["folders"] = folders context["crumbs"] = crumbs context["permalink"] = self.site.link( - "gallery", os.path.basename(gallery), lang) + "gallery", os.path.basename( + os.path.relpath(gallery, self.kw['gallery_path'])), lang) # FIXME: use kw context["enable_comments"] = ( self.site.config["COMMENTS_IN_GALLERIES"]) @@ -219,7 +221,7 @@ class Galleries(Task): template_name, dst, context, - image_list, + dest_img_list, thumbs, file_dep))], 'clean': True, diff --git a/nikola/plugins/task/listings.py b/nikola/plugins/task/listings.py index d8ed43b..86be6c4 100644 --- a/nikola/plugins/task/listings.py +++ b/nikola/plugins/task/listings.py @@ -55,7 +55,7 @@ class Listings(Task): } # Things to ignore in listings - ignored_extensions = (".pyc",) + ignored_extensions = (".pyc", ".pyo") def render_listing(in_name, out_name, folders=[], files=[]): if in_name: diff --git a/nikola/plugins/task/localsearch/files/assets/css/img/search.png b/nikola/plugins/task/localsearch/files/assets/css/img/search.png Binary files differindex 9ab0f2c..9ab0f2c 100755..100644 --- a/nikola/plugins/task/localsearch/files/assets/css/img/search.png +++ b/nikola/plugins/task/localsearch/files/assets/css/img/search.png diff --git a/nikola/plugins/task/localsearch/files/assets/css/tipuesearch.css b/nikola/plugins/task/localsearch/files/assets/css/tipuesearch.css index 2230193..2230193 100755..100644 --- a/nikola/plugins/task/localsearch/files/assets/css/tipuesearch.css +++ b/nikola/plugins/task/localsearch/files/assets/css/tipuesearch.css diff --git a/nikola/plugins/task/localsearch/files/tipue_search.html b/nikola/plugins/task/localsearch/files/tipue_search.html index 789fbe5..789fbe5 100755..100644 --- a/nikola/plugins/task/localsearch/files/tipue_search.html +++ b/nikola/plugins/task/localsearch/files/tipue_search.html diff --git a/nikola/plugins/task/rss.py b/nikola/plugins/task/rss.py index e5f7548..9e4204c 100644 --- a/nikola/plugins/task/rss.py +++ b/nikola/plugins/task/rss.py @@ -58,6 +58,11 @@ class GenerateRSS(Task): "feed_length": self.site.config['FEED_LENGTH'], } self.site.scan_posts() + # Check for any changes in the state of use_in_feeds for any post. + # Issue #934 + kw['use_in_feeds_status'] = ''.join( + ['T' if x.use_in_feeds else 'F' for x in self.site.timeline] + ) yield self.group_task() for lang in kw["translations"]: output_name = os.path.join(kw['output_folder'], diff --git a/nikola/plugins/task/sitemap/__init__.py b/nikola/plugins/task/sitemap/__init__.py index 0164000..147bd50 100644 --- a/nikola/plugins/task/sitemap/__init__.py +++ b/nikola/plugins/task/sitemap/__init__.py @@ -144,26 +144,35 @@ class Sitemap(LateTask): def write_sitemap(): # Have to rescan, because files may have been added between # task dep scanning and task execution - scan_locs() with codecs.open(sitemap_path, 'wb+', 'utf8') as outf: outf.write(header) for k in sorted(locs.keys()): outf.write(locs[k]) outf.write("</urlset>") - # Other tasks can depend on this output, instead of having - # to scan locations. + + # Yield a task to calculate the dependencies of the sitemap + # Other tasks can depend on this output, instead of having + # to scan locations. + def scan_locs_task(): + scan_locs() return {'locations': list(locs.keys())} - scan_locs() + yield { + "basename": "_scan_locs", + "name": "sitemap", + "actions": [(scan_locs_task)] + } + yield self.group_task() task = { "basename": "sitemap", "name": sitemap_path, "targets": [sitemap_path], "actions": [(write_sitemap,)], - "uptodate": [config_changed({1: kw, 2: locs})], + "uptodate": [config_changed(kw)], "clean": True, "task_dep": ["render_site"], + "calc_dep": ["_scan_locs:sitemap"], } yield task diff --git a/nikola/plugins/task/tags.py b/nikola/plugins/task/tags.py index a2444ec..f6b8234 100644 --- a/nikola/plugins/task/tags.py +++ b/nikola/plugins/task/tags.py @@ -311,9 +311,17 @@ class RenderTags(Task): self.site.config['INDEX_FILE']] if _f] def tag_path(self, name, lang): - return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'], self.slugify_name(name) + ".html"] if - _f] + if self.site.config['PRETTY_URLS']: + return [_f for _f in [ + self.site.config['TRANSLATIONS'][lang], + self.site.config['TAG_PATH'], + self.slugify_name(name), + self.site.config['INDEX_FILE']] if _f] + else: + return [_f for _f in [ + self.site.config['TRANSLATIONS'][lang], + self.site.config['TAG_PATH'], + self.slugify_name(name) + ".html"] if _f] def tag_rss_path(self, name, lang): return [_f for _f in [self.site.config['TRANSLATIONS'][lang], diff --git a/nikola/plugins/template/jinja.py b/nikola/plugins/template/jinja.py index 17c33d4..f14adfe 100644 --- a/nikola/plugins/template/jinja.py +++ b/nikola/plugins/template/jinja.py @@ -61,6 +61,11 @@ class JinjaTemplates(TemplateSystem): self.lookup.loader = jinja2.FileSystemLoader(directories, encoding='utf-8') + def set_site(self, site): + """Sets the site.""" + self.site = site + self.lookup.filters.update(self.site.config['TEMPLATE_FILTERS']) + def render_template(self, template_name, output_name, context): """Render the template into output_name using context.""" if jinja2 is None: diff --git a/nikola/plugins/template/mako.py b/nikola/plugins/template/mako.py index 45f4335..5a23230 100644 --- a/nikola/plugins/template/mako.py +++ b/nikola/plugins/template/mako.py @@ -49,6 +49,7 @@ class MakoTemplates(TemplateSystem): lookup = None cache = {} + filters = {} def get_deps(self, filename): text = util.read_file(filename) @@ -81,6 +82,11 @@ class MakoTemplates(TemplateSystem): module_directory=cache_dir, output_encoding='utf-8') + def set_site(self, site): + """Sets the site.""" + self.site = site + self.filters.update(self.site.config['TEMPLATE_FILTERS']) + def render_template(self, template_name, output_name, context): """Render the template into output_name using context.""" context['striphtml'] = striphtml @@ -95,6 +101,8 @@ class MakoTemplates(TemplateSystem): def render_template_to_string(self, template, context): """ Render template to a string using context. """ + context = context.update(self.filters) + return Template(template).render(**context) def template_deps(self, template_name): |
