diff options
Diffstat (limited to 'nikola/plugins')
115 files changed, 1073 insertions, 287 deletions
diff --git a/nikola/plugins/basic_import.py b/nikola/plugins/basic_import.py index 073a539..04f1091 100644 --- a/nikola/plugins/basic_import.py +++ b/nikola/plugins/basic_import.py @@ -48,7 +48,6 @@ links = {} class ImportMixin(object): - """Mixin with common used methods.""" name = "import_mixin" diff --git a/nikola/plugins/command/auto.plugin b/nikola/plugins/command/auto.plugin index 3e2b17d..1081c78 100644 --- a/nikola/plugins/command/auto.plugin +++ b/nikola/plugins/command/auto.plugin @@ -5,7 +5,7 @@ module = auto [Documentation] author = Roberto Alsina version = 2.1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Automatically detect site changes, rebuild and optionally refresh a browser. [Nikola] diff --git a/nikola/plugins/command/auto/__init__.py b/nikola/plugins/command/auto/__init__.py index 71f9624..e339c06 100644 --- a/nikola/plugins/command/auto/__init__.py +++ b/nikola/plugins/command/auto/__init__.py @@ -63,7 +63,7 @@ except ImportError: from nikola.plugin_categories import Command -from nikola.utils import req_missing, get_logger, get_theme_path, STDERR_HANDLER +from nikola.utils import dns_sd, req_missing, get_logger, get_theme_path, STDERR_HANDLER LRJS_PATH = os.path.join(os.path.dirname(__file__), 'livereload.js') error_signal = signal('error') refresh_signal = signal('refresh') @@ -79,13 +79,14 @@ ERROR {} class CommandAuto(Command): - """Automatic rebuilds for Nikola.""" name = "auto" logger = None has_server = True doc_purpose = "builds and serves a site; automatically detects site changes, rebuilds, and optionally refreshes a browser" + dns_sd = None + cmd_options = [ { 'name': 'port', @@ -156,7 +157,7 @@ class CommandAuto(Command): # Do not duplicate entries -- otherwise, multiple rebuilds are triggered watched = set([ - 'templates/', + 'templates/', 'plugins/', ] + [get_theme_path(name) for name in self.site.THEMES]) for item in self.site.config['post_pages']: watched.add(os.path.dirname(item[0])) @@ -208,7 +209,6 @@ class CommandAuto(Command): parent = self class Mixed(WebSocketWSGIApplication): - """A class that supports WS and HTTP protocols on the same port.""" def __call__(self, environ, start_response): @@ -235,9 +235,12 @@ class CommandAuto(Command): webbrowser.open('http://{0}:{1}'.format(host, port)) try: + self.dns_sd = dns_sd(port, (options['ipv6'] or '::' in host)) ws.serve_forever() except KeyboardInterrupt: self.logger.info("Server is shutting down.") + if self.dns_sd: + self.dns_sd.Reset() # This is a hack, but something is locking up in a futex # and exit() doesn't work. os.kill(os.getpid(), 15) @@ -262,6 +265,8 @@ class CommandAuto(Command): fname = os.path.basename(event_path) if (fname.endswith('~') or fname.startswith('.') or + '__pycache__' in event_path or + event_path.endswith(('.pyc', '.pyo', '.pyd')) or os.path.isdir(event_path)): # Skip on folders, these are usually duplicates return self.logger.info('REBUILDING SITE (from {0})'.format(event_path)) @@ -300,11 +305,14 @@ class CommandAuto(Command): mimetype = 'text/html' if p_uri.path == '/robots.txt': - start_response('200 OK', [('Content-type', 'text/plain')]) + start_response('200 OK', [('Content-type', 'text/plain; charset=UTF-8')]) return ['User-Agent: *\nDisallow: /\n'.encode('utf-8')] elif os.path.isfile(f_path): with open(f_path, 'rb') as fd: - start_response('200 OK', [('Content-type', mimetype)]) + if mimetype.startswith('text/') or mimetype.endswith('+xml'): + start_response('200 OK', [('Content-type', "{0}; charset=UTF-8".format(mimetype))]) + else: + start_response('200 OK', [('Content-type', mimetype)]) return [self.file_filter(mimetype, fd.read())] elif p_uri.path == '/livereload.js': with open(LRJS_PATH, 'rb') as fd: @@ -337,7 +345,6 @@ pending = [] class LRSocket(WebSocket): - """Speak Livereload protocol.""" def __init__(self, *a, **kw): @@ -410,7 +417,6 @@ class LRSocket(WebSocket): class OurWatchHandler(FileSystemEventHandler): - """A Nikola-specific handler for Watchdog.""" def __init__(self, function): @@ -424,7 +430,6 @@ class OurWatchHandler(FileSystemEventHandler): class ConfigWatchHandler(FileSystemEventHandler): - """A Nikola-specific handler for Watchdog that handles the config file (as a workaround).""" def __init__(self, configuration_filename, function): diff --git a/nikola/plugins/command/bootswatch_theme.plugin b/nikola/plugins/command/bootswatch_theme.plugin index fc25045..51e6718 100644 --- a/nikola/plugins/command/bootswatch_theme.plugin +++ b/nikola/plugins/command/bootswatch_theme.plugin @@ -5,7 +5,7 @@ module = bootswatch_theme [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Given a swatch name and a parent theme, creates a custom theme. [Nikola] diff --git a/nikola/plugins/command/bootswatch_theme.py b/nikola/plugins/command/bootswatch_theme.py index b5644a1..afd15af 100644 --- a/nikola/plugins/command/bootswatch_theme.py +++ b/nikola/plugins/command/bootswatch_theme.py @@ -37,7 +37,6 @@ LOGGER = utils.get_logger('bootswatch_theme', utils.STDERR_HANDLER) class CommandBootswatchTheme(Command): - """Given a swatch name from bootswatch.com and a parent theme, creates a custom theme.""" name = "bootswatch_theme" @@ -91,12 +90,16 @@ class CommandBootswatchTheme(Command): 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 = 'http://bootswatch.com' + url = 'https://bootswatch.com' if version: url += '/' + version url = '/'.join((url, swatch, fname)) LOGGER.info("Downloading: " + url) - data = requests.get(url).text + r = requests.get(url) + if r.status_code > 299: + LOGGER.error('Error {} getting {}', r.status_code, url) + exit(1) + data = r.text with open(os.path.join('themes', name, 'assets', 'css', fname), 'wb+') as output: output.write(data.encode('utf-8')) diff --git a/nikola/plugins/command/check.plugin b/nikola/plugins/command/check.plugin index e380e64..6d2df82 100644 --- a/nikola/plugins/command/check.plugin +++ b/nikola/plugins/command/check.plugin @@ -5,7 +5,7 @@ module = check [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Check the generated site [Nikola] diff --git a/nikola/plugins/command/check.py b/nikola/plugins/command/check.py index abf183e..bfc6ee2 100644 --- a/nikola/plugins/command/check.py +++ b/nikola/plugins/command/check.py @@ -46,7 +46,10 @@ from nikola.plugin_categories import Command from nikola.utils import get_logger, STDERR_HANDLER -def _call_nikola_list(site): +def _call_nikola_list(site, cache=None): + if cache is not None: + if 'files' in cache and 'deps' in cache: + return cache['files'], cache['deps'] files = [] deps = defaultdict(list) for task in generate_tasks('render_site', site.gen_tasks('render_site', "Task", '')): @@ -57,16 +60,19 @@ def _call_nikola_list(site): files.extend(task.targets) for target in task.targets: deps[target].extend(task.file_dep) + if cache is not None: + cache['files'] = files + cache['deps'] = deps return files, deps -def real_scan_files(site): +def real_scan_files(site, cache=None): """Scan for files.""" task_fnames = set([]) real_fnames = set([]) output_folder = site.config['OUTPUT_FOLDER'] # First check that all targets are generated in the right places - for fname in _call_nikola_list(site)[0]: + for fname in _call_nikola_list(site, cache)[0]: fname = fname.strip() if fname.startswith(output_folder): task_fnames.add(fname) @@ -94,7 +100,6 @@ def fs_relpath_from_url_path(url_path): class CommandCheck(Command): - """Check the generated site.""" name = "check" @@ -162,22 +167,25 @@ class CommandCheck(Command): self.logger.level = 1 else: self.logger.level = 4 + failure = False if options['links']: - failure = self.scan_links(options['find_sources'], options['remote']) + failure |= self.scan_links(options['find_sources'], options['remote']) if options['files']: - failure = self.scan_files() + failure |= self.scan_files() if options['clean']: - failure = self.clean_files() + failure |= self.clean_files() if failure: return 1 existing_targets = set([]) checked_remote_targets = {} + cache = {} def analyze(self, fname, find_sources=False, check_remote=False): """Analyze links on a page.""" rv = False self.whitelist = [re.compile(x) for x in self.site.config['LINK_CHECK_WHITELIST']] + self.internal_redirects = [urljoin('/', _[0]) for _ in self.site.config['REDIRECTIONS']] base_url = urlparse(self.site.config['BASE_URL']) self.existing_targets.add(self.site.config['SITE_URL']) self.existing_targets.add(self.site.config['BASE_URL']) @@ -185,7 +193,7 @@ class CommandCheck(Command): deps = {} if find_sources: - deps = _call_nikola_list(self.site)[1] + deps = _call_nikola_list(self.site, self.cache)[1] if url_type in ('absolute', 'full_path'): url_netloc_to_root = urlparse(self.site.config['BASE_URL']).path @@ -203,31 +211,70 @@ class CommandCheck(Command): # Quietly ignore files that don’t exist; use `nikola check -f` instead (Issue #1831) return False - d = lxml.html.fromstring(open(filename, 'rb').read()) - for l in d.iterlinks(): + if '.html' == fname[-5:]: + d = lxml.html.fromstring(open(filename, 'rb').read()) + extra_objs = lxml.html.fromstring('<html/>') + + # Turn elements with a srcset attribute into individual img elements with src attributes + for obj in list(d.xpath('(*//img|*//source)')): + if 'srcset' in obj.attrib: + for srcset_item in obj.attrib['srcset'].split(','): + extra_objs.append(lxml.etree.Element('img', src=srcset_item.strip().split(' ')[0])) + link_elements = list(d.iterlinks()) + list(extra_objs.iterlinks()) + # Extract links from XML formats to minimal HTML, allowing those to go through the link checks + elif '.atom' == filename[-5:]: + d = lxml.etree.parse(filename) + link_elements = lxml.html.fromstring('<html/>') + for elm in d.findall('*//{http://www.w3.org/2005/Atom}link'): + feed_link = elm.attrib['href'].split('?')[0].strip() # strip FEED_LINKS_APPEND_QUERY + link_elements.append(lxml.etree.Element('a', href=feed_link)) + link_elements = list(link_elements.iterlinks()) + elif filename.endswith('sitemap.xml') or filename.endswith('sitemapindex.xml'): + d = lxml.etree.parse(filename) + link_elements = lxml.html.fromstring('<html/>') + for elm in d.getroot().findall("*//{http://www.sitemaps.org/schemas/sitemap/0.9}loc"): + link_elements.append(lxml.etree.Element('a', href=elm.text.strip())) + link_elements = list(link_elements.iterlinks()) + else: # unsupported file type + return False + + for l in link_elements: target = l[2] if target == "#": continue - target, _ = urldefrag(target) + target = urldefrag(target)[0] + + if any([urlparse(target).netloc.endswith(_) for _ in ['example.com', 'example.net', 'example.org']]): + self.logger.info("Not testing example address \"{0}\".".format(target)) + continue + + # absolute URL to root-relative + if target.startswith(base_url.geturl()): + target = target.replace(base_url.geturl(), '/') + parsed = urlparse(target) # Warn about links from https to http (mixed-security) if base_url.netloc == parsed.netloc and base_url.scheme == "https" and parsed.scheme == "http": self.logger.warn("Mixed-content security for link in {0}: {1}".format(filename, target)) + # Link to an internal REDIRECTIONS page + if target in self.internal_redirects: + redir_status_code = 301 + redir_target = [_dest for _target, _dest in self.site.config['REDIRECTIONS'] if urljoin('/', _target) == target][0] + self.logger.warn("Remote link moved PERMANENTLY to \"{0}\" and should be updated in {1}: {2} [HTTP: 301]".format(redir_target, filename, target)) + # Absolute links to other domains, skip # Absolute links when using only paths, skip. if ((parsed.scheme or target.startswith('//')) and parsed.netloc != base_url.netloc) or \ ((parsed.scheme or target.startswith('//')) and url_type in ('rel_path', 'full_path')): if not check_remote or parsed.scheme not in ["http", "https"]: continue - if parsed.netloc == base_url.netloc: # absolute URL to self.site - continue if target in self.checked_remote_targets: # already checked this exact target - if self.checked_remote_targets[target] in [301, 307]: + if self.checked_remote_targets[target] in [301, 308]: self.logger.warn("Remote link PERMANENTLY redirected in {0}: {1} [Error {2}]".format(filename, target, self.checked_remote_targets[target])) - elif self.checked_remote_targets[target] in [302, 308]: - self.logger.info("Remote link temporarily redirected in {1}: {2} [HTTP: {3}]".format(filename, target, self.checked_remote_targets[target])) + elif self.checked_remote_targets[target] in [302, 307]: + self.logger.notice("Remote link temporarily redirected in {1}: {2} [HTTP: {3}]".format(filename, target, self.checked_remote_targets[target])) elif self.checked_remote_targets[target] > 399: self.logger.error("Broken link in {0}: {1} [Error {2}]".format(filename, target, self.checked_remote_targets[target])) continue @@ -255,7 +302,7 @@ class CommandCheck(Command): if redir_status_code in [301, 308]: self.logger.warn("Remote link moved PERMANENTLY to \"{0}\" and should be updated in {1}: {2} [HTTP: {3}]".format(resp.url, filename, target, redir_status_code)) if redir_status_code in [302, 307]: - self.logger.info("Remote link temporarily redirected to \"{0}\" in {1}: {2} [HTTP: {3}]".format(resp.url, filename, target, redir_status_code)) + self.logger.notice("Remote link temporarily redirected to \"{0}\" in {1}: {2} [HTTP: {3}]".format(resp.url, filename, target, redir_status_code)) self.checked_remote_targets[resp.url] = resp.status_code self.checked_remote_targets[target] = redir_status_code else: @@ -275,8 +322,9 @@ class CommandCheck(Command): target_filename = os.path.abspath( os.path.join(self.site.config['OUTPUT_FOLDER'], unquote(target.lstrip('/')))) else: # Relative path + unquoted_target = unquote(target).encode('utf-8') if sys.version_info.major >= 3 else unquote(target).decode('utf-8') target_filename = os.path.abspath( - os.path.join(os.path.dirname(filename), unquote(target))) + os.path.join(os.path.dirname(filename).encode('utf-8'), unquoted_target)) elif url_type in ('full_path', 'absolute'): if url_type == 'absolute': @@ -292,9 +340,10 @@ class CommandCheck(Command): if any(re.search(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.logger.info(u"Good link {0} => {1}".format(target, target_filename)) self.existing_targets.add(target_filename) else: rv = True @@ -304,7 +353,7 @@ class CommandCheck(Command): self.logger.warn("\n".join(deps[filename])) self.logger.warn("===============================\n") except Exception as exc: - self.logger.error("Error with: {0} {1}".format(filename, exc)) + self.logger.error(u"Error with: {0} {1}".format(filename, exc)) return rv def scan_links(self, find_sources=False, check_remote=False): @@ -315,10 +364,21 @@ class CommandCheck(Command): failure = False # Maybe we should just examine all HTML files output_folder = self.site.config['OUTPUT_FOLDER'] - for fname in _call_nikola_list(self.site)[0]: - if fname.startswith(output_folder) and '.html' == fname[-5:]: - if self.analyze(fname, find_sources, check_remote): - failure = True + + if urlparse(self.site.config['BASE_URL']).netloc == 'example.com': + self.logger.error("You've not changed the SITE_URL (or BASE_URL) setting from \"example.com\"!") + + for fname in _call_nikola_list(self.site, self.cache)[0]: + if fname.startswith(output_folder): + if '.html' == fname[-5:]: + if self.analyze(fname, find_sources, check_remote): + failure = True + if '.atom' == fname[-5:]: + if self.analyze(fname, find_sources, False): + failure = True + if fname.endswith('sitemap.xml') or fname.endswith('sitemapindex.xml'): + if self.analyze(fname, find_sources, False): + failure = True if not failure: self.logger.info("All links checked.") return failure @@ -328,7 +388,7 @@ class CommandCheck(Command): failure = False self.logger.info("Checking Files:") self.logger.info("===============\n") - only_on_output, only_on_input = real_scan_files(self.site) + only_on_output, only_on_input = real_scan_files(self.site, self.cache) # Ignore folders only_on_output = [p for p in only_on_output if not os.path.isdir(p)] @@ -351,7 +411,7 @@ class CommandCheck(Command): def clean_files(self): """Remove orphaned files.""" - only_on_output, _ = real_scan_files(self.site) + only_on_output, _ = real_scan_files(self.site, self.cache) for f in only_on_output: self.logger.info('removed: {0}'.format(f)) os.unlink(f) diff --git a/nikola/plugins/command/console.plugin b/nikola/plugins/command/console.plugin index 333762c..9bcc909 100644 --- a/nikola/plugins/command/console.plugin +++ b/nikola/plugins/command/console.plugin @@ -5,7 +5,7 @@ module = console [Documentation] author = Chris Warrick, Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Start a debugging python console [Nikola] diff --git a/nikola/plugins/command/console.py b/nikola/plugins/command/console.py index 539fa08..3d3daab 100644 --- a/nikola/plugins/command/console.py +++ b/nikola/plugins/command/console.py @@ -38,7 +38,6 @@ LOGGER = get_logger('console', STDERR_HANDLER) class CommandConsole(Command): - """Start debugging console.""" name = "console" diff --git a/nikola/plugins/command/deploy.plugin b/nikola/plugins/command/deploy.plugin index 4743ca2..8bdc0e2 100644 --- a/nikola/plugins/command/deploy.plugin +++ b/nikola/plugins/command/deploy.plugin @@ -5,7 +5,7 @@ module = deploy [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Deploy the site [Nikola] diff --git a/nikola/plugins/command/deploy.py b/nikola/plugins/command/deploy.py index 821ea11..757c0d2 100644 --- a/nikola/plugins/command/deploy.py +++ b/nikola/plugins/command/deploy.py @@ -41,7 +41,6 @@ from nikola.utils import get_logger, remove_file, unicode_str, makedirs, STDERR_ class CommandDeploy(Command): - """Deploy site.""" name = "deploy" diff --git a/nikola/plugins/command/github_deploy.plugin b/nikola/plugins/command/github_deploy.plugin index e793548..21e246c 100644 --- a/nikola/plugins/command/github_deploy.plugin +++ b/nikola/plugins/command/github_deploy.plugin @@ -5,7 +5,7 @@ module = github_deploy [Documentation] author = Puneeth Chaganti version = 1,0 -website = http://getnikola.com +website = https://getnikola.com/ description = Deploy the site to GitHub pages. [Nikola] diff --git a/nikola/plugins/command/github_deploy.py b/nikola/plugins/command/github_deploy.py index 0ab9332..2fe0d4e 100644 --- a/nikola/plugins/command/github_deploy.py +++ b/nikola/plugins/command/github_deploy.py @@ -57,7 +57,6 @@ def check_ghp_import_installed(): class CommandGitHubDeploy(Command): - """Deploy site to GitHub Pages.""" name = 'github_deploy' diff --git a/nikola/plugins/command/import_wordpress.plugin b/nikola/plugins/command/import_wordpress.plugin index 6c4384e..eab9d17 100644 --- a/nikola/plugins/command/import_wordpress.plugin +++ b/nikola/plugins/command/import_wordpress.plugin @@ -5,7 +5,7 @@ module = import_wordpress [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Import a wordpress site from a XML dump (requires markdown). [Nikola] diff --git a/nikola/plugins/command/import_wordpress.py b/nikola/plugins/command/import_wordpress.py index a652ec8..69ef144 100644 --- a/nikola/plugins/command/import_wordpress.py +++ b/nikola/plugins/command/import_wordpress.py @@ -50,7 +50,7 @@ except ImportError: from nikola.plugin_categories import Command from nikola import utils -from nikola.utils import req_missing +from nikola.utils import req_missing, unicode_str 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, format_default_translations_config @@ -88,7 +88,6 @@ def install_plugin(site, plugin_name, output_dir=None, show_install_notes=False) class CommandImportWordpress(Command, ImportMixin): - """Import a WordPress dump.""" name = "import_wordpress" @@ -191,6 +190,12 @@ class CommandImportWordpress(Command, ImportMixin): 'type': bool, 'help': "Automatically installs the WordPress page compiler (either locally or in the new site) if required by other options.\nWarning: the compiler is GPL software!", }, + { + 'name': 'tag_saniziting_strategy', + 'long': 'tag-saniziting-strategy', + 'default': 'first', + 'help': 'lower: Convert all tag and category names to lower case\nfirst: Keep first spelling of tag or category name', + }, ] all_tags = set([]) @@ -239,6 +244,8 @@ class CommandImportWordpress(Command, ImportMixin): self.install_wordpress_compiler = options.get('install_wordpress_compiler', False) self.wordpress_page_compiler = None + self.tag_saniziting_strategy = options.get('tag_saniziting_strategy', 'first') + self.auth = None if options.get('download_auth') is not None: username_password = options.get('download_auth') @@ -337,7 +344,10 @@ class CommandImportWordpress(Command, ImportMixin): # Add tag redirects for tag in self.all_tags: try: - tag_str = tag.decode('utf8') + if isinstance(tag, utils.bytes_str): + tag_str = tag.decode('utf8', 'replace') + else: + tag_str = tag except AttributeError: tag_str = tag tag = utils.slugify(tag_str) @@ -604,7 +614,7 @@ class CommandImportWordpress(Command, ImportMixin): def transform_code(self, content): """Transform code blocks.""" - # http://en.support.wordpress.com/code/posting-source-code/. There are + # https://en.support.wordpress.com/code/posting-source-code/. There are # a ton of things not supported here. We only do a basic [code # lang="x"] -> ```x translation, and remove quoted html entities (<, # >, &, and "). @@ -686,7 +696,7 @@ class CommandImportWordpress(Command, ImportMixin): elif approved == 'spam' or approved == 'trash': pass else: - LOGGER.warn("Unknown comment approved status: " + str(approved)) + LOGGER.warn("Unknown comment approved status: {0}".format(approved)) parent = int(get_text_tag(comment, "{{{0}}}comment_parent".format(wordpress_namespace), 0)) if parent == 0: parent = None @@ -707,7 +717,7 @@ class CommandImportWordpress(Command, ImportMixin): """Write comment header line.""" if header_content is None: return - header_content = str(header_content).replace('\n', ' ') + header_content = unicode_str(header_content).replace('\n', ' ') line = '.. ' + header_field + ': ' + header_content + '\n' fd.write(line.encode('utf8')) @@ -747,6 +757,24 @@ class CommandImportWordpress(Command, ImportMixin): tags_cats = tags + categories return tags_cats, other_meta + _tag_sanitize_map = {True: {}, False: {}} + + def _sanitize(self, tag, is_category): + if self.tag_saniziting_strategy == 'lower': + return tag.lower() + if tag.lower() not in self._tag_sanitize_map[is_category]: + self._tag_sanitize_map[is_category][tag.lower()] = [tag] + return tag + previous = self._tag_sanitize_map[is_category][tag.lower()] + if self.tag_saniziting_strategy == 'first': + if tag != previous[0]: + LOGGER.warn("Changing spelling of {0} name '{1}' to {2}.".format('category' if is_category else 'tag', tag, previous[0])) + return previous[0] + else: + LOGGER.error("Unknown tag sanitizing strategy '{0}'!".format(self.tag_saniziting_strategy)) + sys.exit(1) + return tag + def import_postpage_item(self, item, wordpress_namespace, out_folder=None, attachments=None): """Take an item from the feed and creates a post file.""" if out_folder is None: @@ -760,7 +788,10 @@ class CommandImportWordpress(Command, ImportMixin): path = unquote(parsed.path.strip('/')) try: - path = path.decode('utf8') + if isinstance(path, utils.bytes_str): + path = path.decode('utf8', 'replace') + else: + path = path except AttributeError: pass @@ -831,15 +862,24 @@ class CommandImportWordpress(Command, ImportMixin): type = tag.attrib['domain'] if text == 'Uncategorized' and type == 'category': continue - self.all_tags.add(text) if type == 'category': - categories.append(type) + categories.append(text) else: tags.append(text) if '$latex' in content: tags.append('mathjax') + for i, cat in enumerate(categories[:]): + cat = self._sanitize(cat, True) + categories[i] = cat + self.all_tags.add(cat) + + for i, tag in enumerate(tags[:]): + tag = self._sanitize(tag, False) + tags[i] = tag + self.all_tags.add(tag) + # Find post format if it's there post_format = 'wp' format_tag = [x for x in item.findall('*//{%s}meta_key' % wordpress_namespace) if x.text == '_tc_post_format'] @@ -905,7 +945,7 @@ class CommandImportWordpress(Command, ImportMixin): comments.append(comment) for comment in comments: - comment_filename = slug + "." + str(comment['id']) + ".wpcomment" + comment_filename = "{0}.{1}.wpcomment".format(slug, comment['id']) self._write_comment(os.path.join(self.output_folder, out_folder, comment_filename), comment) return (out_folder, slug) diff --git a/nikola/plugins/command/init.plugin b/nikola/plugins/command/init.plugin index a5404c4..a8b1523 100644 --- a/nikola/plugins/command/init.plugin +++ b/nikola/plugins/command/init.plugin @@ -5,7 +5,7 @@ module = init [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create a new site. [Nikola] diff --git a/nikola/plugins/command/init.py b/nikola/plugins/command/init.py index 91ccdb4..2dbee43 100644 --- a/nikola/plugins/command/init.py +++ b/nikola/plugins/command/init.py @@ -41,7 +41,7 @@ from pkg_resources import resource_filename import tarfile import nikola -from nikola.nikola import DEFAULT_TRANSLATIONS_PATTERN, DEFAULT_INDEX_READ_MORE_LINK, DEFAULT_RSS_READ_MORE_LINK, LEGAL_VALUES, urlsplit, urlunsplit +from nikola.nikola import DEFAULT_TRANSLATIONS_PATTERN, DEFAULT_INDEX_READ_MORE_LINK, DEFAULT_FEED_READ_MORE_LINK, LEGAL_VALUES, urlsplit, urlunsplit from nikola.plugin_categories import Command from nikola.utils import ask, ask_yesno, get_logger, makedirs, STDERR_HANDLER, load_messages from nikola.packages.tzlocal import get_localzone @@ -71,7 +71,7 @@ SAMPLE_CONF = { 'CATEGORY_OUTPUT_FLAT_HIERARCHY': False, 'TRANSLATIONS_PATTERN': DEFAULT_TRANSLATIONS_PATTERN, 'INDEX_READ_MORE_LINK': DEFAULT_INDEX_READ_MORE_LINK, - 'RSS_READ_MORE_LINK': DEFAULT_RSS_READ_MORE_LINK, + 'FEED_READ_MORE_LINK': DEFAULT_FEED_READ_MORE_LINK, 'POSTS': """( ("posts/*.rst", "posts", "post.tmpl"), ("posts/*.txt", "posts", "post.tmpl"), @@ -210,17 +210,16 @@ def prepare_config(config): """Parse sample config with JSON.""" p = config.copy() p.update({k: json.dumps(v, ensure_ascii=False) for k, v in p.items() - if k not in ('POSTS', 'PAGES', 'COMPILERS', 'TRANSLATIONS', 'NAVIGATION_LINKS', '_SUPPORTED_LANGUAGES', '_SUPPORTED_COMMENT_SYSTEMS', 'INDEX_READ_MORE_LINK', 'RSS_READ_MORE_LINK')}) + if k not in ('POSTS', 'PAGES', 'COMPILERS', 'TRANSLATIONS', 'NAVIGATION_LINKS', '_SUPPORTED_LANGUAGES', '_SUPPORTED_COMMENT_SYSTEMS', 'INDEX_READ_MORE_LINK', 'FEED_READ_MORE_LINK')}) # READ_MORE_LINKs require some special treatment. p['INDEX_READ_MORE_LINK'] = "'" + p['INDEX_READ_MORE_LINK'].replace("'", "\\'") + "'" - p['RSS_READ_MORE_LINK'] = "'" + p['RSS_READ_MORE_LINK'].replace("'", "\\'") + "'" + p['FEED_READ_MORE_LINK'] = "'" + p['FEED_READ_MORE_LINK'].replace("'", "\\'") + "'" # fix booleans and None p.update({k: str(v) for k, v in config.items() if isinstance(v, bool) or v is None}) return p class CommandInit(Command): - """Create a new site.""" name = "init" @@ -358,7 +357,7 @@ class CommandInit(Command): def tzhandler(default, toconf): print("\nPlease choose the correct time zone for your blog. Nikola uses the tz database.") print("You can find your time zone here:") - print("http://en.wikipedia.org/wiki/List_of_tz_database_time_zones") + print("https://en.wikipedia.org/wiki/List_of_tz_database_time_zones") print("") answered = False while not answered: diff --git a/nikola/plugins/command/install_theme.plugin b/nikola/plugins/command/install_theme.plugin index 8434f2e..aa68773 100644 --- a/nikola/plugins/command/install_theme.plugin +++ b/nikola/plugins/command/install_theme.plugin @@ -5,7 +5,7 @@ module = install_theme [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Install a theme into the current site. [Nikola] diff --git a/nikola/plugins/command/install_theme.py b/nikola/plugins/command/install_theme.py index f02252e..bad335c 100644 --- a/nikola/plugins/command/install_theme.py +++ b/nikola/plugins/command/install_theme.py @@ -43,7 +43,6 @@ LOGGER = utils.get_logger('install_theme', utils.STDERR_HANDLER) class CommandInstallTheme(Command): - """Install a theme.""" name = "install_theme" diff --git a/nikola/plugins/command/new_page.plugin b/nikola/plugins/command/new_page.plugin index 145a419..3eaecb4 100644 --- a/nikola/plugins/command/new_page.plugin +++ b/nikola/plugins/command/new_page.plugin @@ -5,7 +5,7 @@ module = new_page [Documentation] author = Roberto Alsina, Chris Warrick version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create a new page. [Nikola] diff --git a/nikola/plugins/command/new_page.py b/nikola/plugins/command/new_page.py index 811e28b..8843421 100644 --- a/nikola/plugins/command/new_page.py +++ b/nikola/plugins/command/new_page.py @@ -32,7 +32,6 @@ from nikola.plugin_categories import Command class CommandNewPage(Command): - """Create a new page.""" name = "new_page" diff --git a/nikola/plugins/command/new_post.plugin b/nikola/plugins/command/new_post.plugin index d88469f..e9c3af5 100644 --- a/nikola/plugins/command/new_post.plugin +++ b/nikola/plugins/command/new_post.plugin @@ -5,7 +5,7 @@ module = new_post [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create a new post. [Nikola] diff --git a/nikola/plugins/command/new_post.py b/nikola/plugins/command/new_post.py index f9fe3ff..30d009d 100644 --- a/nikola/plugins/command/new_post.py +++ b/nikola/plugins/command/new_post.py @@ -114,7 +114,6 @@ def get_date(schedule=False, rule=None, last_date=None, tz=None, iso8601=False): class CommandNewPost(Command): - """Create a new post.""" name = "new_post" diff --git a/nikola/plugins/command/orphans.plugin b/nikola/plugins/command/orphans.plugin index 669429d..d20c539 100644 --- a/nikola/plugins/command/orphans.plugin +++ b/nikola/plugins/command/orphans.plugin @@ -5,7 +5,7 @@ module = orphans [Documentation] author = Roberto Alsina, Chris Warrick version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = List all orphans [Nikola] diff --git a/nikola/plugins/command/orphans.py b/nikola/plugins/command/orphans.py index b12cc67..f061d39 100644 --- a/nikola/plugins/command/orphans.py +++ b/nikola/plugins/command/orphans.py @@ -34,7 +34,6 @@ from nikola.plugins.command.check import real_scan_files class CommandOrphans(Command): - """List all orphans.""" name = "orphans" diff --git a/nikola/plugins/command/plugin.plugin b/nikola/plugins/command/plugin.plugin index d44dcf3..016bcaa 100644 --- a/nikola/plugins/command/plugin.plugin +++ b/nikola/plugins/command/plugin.plugin @@ -5,7 +5,7 @@ module = plugin [Documentation] author = Roberto Alsina and Chris Warrick version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Manage Nikola plugins [Nikola] diff --git a/nikola/plugins/command/plugin.py b/nikola/plugins/command/plugin.py index f892ee9..1df7b71 100644 --- a/nikola/plugins/command/plugin.py +++ b/nikola/plugins/command/plugin.py @@ -45,7 +45,6 @@ LOGGER = utils.get_logger('plugin', utils.STDERR_HANDLER) class CommandPlugin(Command): - """Manage plugins.""" json = None diff --git a/nikola/plugins/command/rst2html.plugin b/nikola/plugins/command/rst2html.plugin index 02c9276..a095705 100644 --- a/nikola/plugins/command/rst2html.plugin +++ b/nikola/plugins/command/rst2html.plugin @@ -5,7 +5,7 @@ module = rst2html [Documentation] author = Chris Warrick version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Compile reStructuredText to HTML using the Nikola architecture [Nikola] diff --git a/nikola/plugins/command/rst2html/__init__.py b/nikola/plugins/command/rst2html/__init__.py index 06afffd..68ea13d 100644 --- a/nikola/plugins/command/rst2html/__init__.py +++ b/nikola/plugins/command/rst2html/__init__.py @@ -36,7 +36,6 @@ from nikola.plugin_categories import Command class CommandRst2Html(Command): - """Compile reStructuredText to HTML, using Nikola architecture.""" name = "rst2html" @@ -65,7 +64,7 @@ class CommandRst2Html(Command): parser = lxml.html.HTMLParser(remove_blank_text=True) doc = lxml.html.document_fromstring(template_output, parser) html = b'<!DOCTYPE html>\n' + lxml.html.tostring(doc, encoding='utf8', method='html', pretty_print=True) - print(html) + print(html.decode('utf-8')) if error_level < 3: return 0 else: diff --git a/nikola/plugins/command/serve.plugin b/nikola/plugins/command/serve.plugin index aca71ec..a4a726f 100644 --- a/nikola/plugins/command/serve.plugin +++ b/nikola/plugins/command/serve.plugin @@ -5,7 +5,7 @@ module = serve [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Start test server. [Nikola] diff --git a/nikola/plugins/command/serve.py b/nikola/plugins/command/serve.py index 0441c93..b647bb7 100644 --- a/nikola/plugins/command/serve.py +++ b/nikola/plugins/command/serve.py @@ -45,24 +45,23 @@ except ImportError: from nikola.plugin_categories import Command -from nikola.utils import get_logger, STDERR_HANDLER +from nikola.utils import dns_sd, get_logger, STDERR_HANDLER class IPv6Server(HTTPServer): - """An IPv6 HTTPServer.""" address_family = socket.AF_INET6 class CommandServe(Command): - """Start test server.""" name = "serve" doc_usage = "[options]" doc_purpose = "start the test webserver" logger = None + dns_sd = None cmd_options = ( { @@ -152,14 +151,16 @@ class CommandServe(Command): raise e else: try: + self.dns_sd = dns_sd(options['port'], (options['ipv6'] or '::' in options['address'])) httpd.serve_forever() except KeyboardInterrupt: self.logger.info("Server is shutting down.") + if self.dns_sd: + self.dns_sd.Reset() return 130 class OurHTTPRequestHandler(SimpleHTTPRequestHandler): - """A request handler, modified for Nikola.""" extensions_map = dict(SimpleHTTPRequestHandler.extensions_map) @@ -242,7 +243,10 @@ class OurHTTPRequestHandler(SimpleHTTPRequestHandler): f.seek(0) self.send_response(200) - self.send_header("Content-type", ctype) + if ctype.startswith('text/') or ctype.endswith('+xml'): + self.send_header("Content-Type", "{0}; charset=UTF-8".format(ctype)) + else: + self.send_header("Content-Type", ctype) if os.path.splitext(path)[1] == '.svgz': # Special handling for svgz to make it work nice with browsers. self.send_header("Content-Encoding", 'gzip') diff --git a/nikola/plugins/command/status.py b/nikola/plugins/command/status.py index 55e7f95..40f4f77 100644 --- a/nikola/plugins/command/status.py +++ b/nikola/plugins/command/status.py @@ -36,7 +36,6 @@ from nikola.plugin_categories import Command class CommandStatus(Command): - """Display site status.""" name = "status" diff --git a/nikola/plugins/command/version.plugin b/nikola/plugins/command/version.plugin index 4708bdb..d78b79b 100644 --- a/nikola/plugins/command/version.plugin +++ b/nikola/plugins/command/version.plugin @@ -5,7 +5,7 @@ module = version [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Show nikola version [Nikola] diff --git a/nikola/plugins/command/version.py b/nikola/plugins/command/version.py index ad08f64..b83d622 100644 --- a/nikola/plugins/command/version.py +++ b/nikola/plugins/command/version.py @@ -38,7 +38,6 @@ URL = 'https://pypi.python.org/pypi?:action=doap&name=Nikola' class CommandVersion(Command): - """Print Nikola version.""" name = "version" diff --git a/nikola/plugins/compile/html.plugin b/nikola/plugins/compile/html.plugin index 53ade61..f95bdd5 100644 --- a/nikola/plugins/compile/html.plugin +++ b/nikola/plugins/compile/html.plugin @@ -5,7 +5,7 @@ module = html [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Compile HTML into HTML (just copy) [Nikola] diff --git a/nikola/plugins/compile/html.py b/nikola/plugins/compile/html.py index 5f8b244..6ff5de8 100644 --- a/nikola/plugins/compile/html.py +++ b/nikola/plugins/compile/html.py @@ -36,7 +36,6 @@ from nikola.utils import makedirs, write_metadata class CompileHtml(PageCompiler): - """Compile HTML into HTML.""" name = "html" diff --git a/nikola/plugins/compile/ipynb.py b/nikola/plugins/compile/ipynb.py index a9dedde..1023b31 100644 --- a/nikola/plugins/compile/ipynb.py +++ b/nikola/plugins/compile/ipynb.py @@ -53,7 +53,6 @@ from nikola.utils import makedirs, req_missing, get_logger, STDERR_HANDLER class CompileIPynb(PageCompiler): - """Compile IPynb into HTML.""" name = "ipynb" diff --git a/nikola/plugins/compile/markdown.plugin b/nikola/plugins/compile/markdown.plugin index f7d11b1..2607413 100644 --- a/nikola/plugins/compile/markdown.plugin +++ b/nikola/plugins/compile/markdown.plugin @@ -5,7 +5,7 @@ module = markdown [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Compile Markdown into HTML [Nikola] diff --git a/nikola/plugins/compile/markdown/__init__.py b/nikola/plugins/compile/markdown/__init__.py index c1425a1..93438a3 100644 --- a/nikola/plugins/compile/markdown/__init__.py +++ b/nikola/plugins/compile/markdown/__init__.py @@ -44,7 +44,6 @@ from nikola.utils import makedirs, req_missing, write_metadata class CompileMarkdown(PageCompiler): - """Compile Markdown into HTML.""" name = "markdown" diff --git a/nikola/plugins/compile/markdown/mdx_gist.plugin b/nikola/plugins/compile/markdown/mdx_gist.plugin index 7fe676c..85b5450 100644 --- a/nikola/plugins/compile/markdown/mdx_gist.plugin +++ b/nikola/plugins/compile/markdown/mdx_gist.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Roberto Alsina version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Extension for embedding gists diff --git a/nikola/plugins/compile/markdown/mdx_gist.py b/nikola/plugins/compile/markdown/mdx_gist.py index f439fa2..a930de5 100644 --- a/nikola/plugins/compile/markdown/mdx_gist.py +++ b/nikola/plugins/compile/markdown/mdx_gist.py @@ -22,7 +22,7 @@ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Warning: URL formats of "raw" gists are undocummented and subject to change. -# See also: http://developer.github.com/v3/gists/ +# See also: https://developer.github.com/v3/gists/ # # Inspired by "[Python] reStructuredText GitHub Gist directive" # (https://gist.github.com/brianhsu/1407759), public domain by Brian Hsu @@ -217,7 +217,6 @@ GIST_RST_RE = r'(?m)^\.\.\s*gist::\s*(?P<gist_id>[^\]\s]+)(?:\s*(?P<filename>.+? class GistFetchException(Exception): - """Raised when attempt to fetch content of a Gist from github.com fails.""" def __init__(self, url, status_code): @@ -228,7 +227,6 @@ class GistFetchException(Exception): class GistPattern(Pattern): - """InlinePattern for footnote markers in a document's body text.""" def __init__(self, pattern, configs): @@ -290,7 +288,6 @@ class GistPattern(Pattern): class GistExtension(MarkdownExtension, Extension): - """Gist extension for Markdown.""" def __init__(self, configs={}): diff --git a/nikola/plugins/compile/markdown/mdx_nikola.plugin b/nikola/plugins/compile/markdown/mdx_nikola.plugin index 12e4fb6..3c5c638 100644 --- a/nikola/plugins/compile/markdown/mdx_nikola.plugin +++ b/nikola/plugins/compile/markdown/mdx_nikola.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Roberto Alsina version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Nikola-specific Markdown extensions diff --git a/nikola/plugins/compile/markdown/mdx_nikola.py b/nikola/plugins/compile/markdown/mdx_nikola.py index 54cc18c..7984121 100644 --- a/nikola/plugins/compile/markdown/mdx_nikola.py +++ b/nikola/plugins/compile/markdown/mdx_nikola.py @@ -24,25 +24,31 @@ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Markdown Extension for Nikola-specific post-processing.""" +"""Markdown Extension for Nikola. + +- Specific post-processing. +- Strikethrough inline patterns. +""" from __future__ import unicode_literals import re try: from markdown.postprocessors import Postprocessor + from markdown.inlinepatterns import SimpleTagPattern from markdown.extensions import Extension except ImportError: # No need to catch this, if you try to use this without Markdown, # the markdown compiler will fail first - Postprocessor = Extension = object + Postprocessor = SimpleTagPattern = Extension = object from nikola.plugin_categories import MarkdownExtension + CODERE = re.compile('<div class="codehilite"><pre>(.*?)</pre></div>', flags=re.MULTILINE | re.DOTALL) +STRIKE_RE = r"(~{2})(.+?)(~{2})" # ~~strike~~ class NikolaPostProcessor(Postprocessor): - """Nikola-specific post-processing for Markdown.""" def run(self, text): @@ -57,13 +63,22 @@ class NikolaPostProcessor(Postprocessor): class NikolaExtension(MarkdownExtension, Extension): + """Nikola Markdown extensions.""" - """Extension for injecting the postprocessor.""" - - def extendMarkdown(self, md, md_globals): + def _add_nikola_post_processor(self, md): """Extend Markdown with the postprocessor.""" pp = NikolaPostProcessor() md.postprocessors.add('nikola_post_processor', pp, '_end') + + def _add_strikethrough_inline_pattern(self, md): + """Support PHP-Markdown style strikethrough, for example: ``~~strike~~``.""" + pattern = SimpleTagPattern(STRIKE_RE, 'del') + md.inlinePatterns.add('strikethrough', pattern, '_end') + + def extendMarkdown(self, md, md_globals): + """Extend markdown to Nikola flavours.""" + self._add_nikola_post_processor(md) + self._add_strikethrough_inline_pattern(md) md.registerExtension(self) diff --git a/nikola/plugins/compile/markdown/mdx_podcast.plugin b/nikola/plugins/compile/markdown/mdx_podcast.plugin index c92a8a0..c4ee7e9 100644 --- a/nikola/plugins/compile/markdown/mdx_podcast.plugin +++ b/nikola/plugins/compile/markdown/mdx_podcast.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Roberto Alsina version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Markdown extensions for embedding podcasts and other audio files diff --git a/nikola/plugins/compile/markdown/mdx_podcast.py b/nikola/plugins/compile/markdown/mdx_podcast.py index 61afdbf..0f68e40 100644 --- a/nikola/plugins/compile/markdown/mdx_podcast.py +++ b/nikola/plugins/compile/markdown/mdx_podcast.py @@ -30,10 +30,10 @@ Extension to Python Markdown for Embedded Audio. Basic Example: >>> import markdown ->>> text = "[podcast]http://archive.org/download/Rebeldes_Stereotipos/rs20120609_1.mp3[/podcast]" +>>> text = "[podcast]https://archive.org/download/Rebeldes_Stereotipos/rs20120609_1.mp3[/podcast]" >>> html = markdown.markdown(text, [PodcastExtension()]) >>> print(html) -<p><audio controls=""><source src="http://archive.org/download/Rebeldes_Stereotipos/rs20120609_1.mp3" type="audio/mpeg"></source></audio></p> +<p><audio controls=""><source src="https://archive.org/download/Rebeldes_Stereotipos/rs20120609_1.mp3" type="audio/mpeg"></source></audio></p> """ from __future__ import print_function, unicode_literals @@ -51,7 +51,6 @@ PODCAST_RE = r'\[podcast\](?P<url>.+)\[/podcast\]' class PodcastPattern(Pattern): - """InlinePattern for footnote markers in a document's body text.""" def __init__(self, pattern, configs): @@ -70,7 +69,6 @@ class PodcastPattern(Pattern): class PodcastExtension(MarkdownExtension, Extension): - """"Podcast extension for Markdown.""" def __init__(self, configs={}): diff --git a/nikola/plugins/compile/pandoc.plugin b/nikola/plugins/compile/pandoc.plugin index 3ff3668..2a69095 100644 --- a/nikola/plugins/compile/pandoc.plugin +++ b/nikola/plugins/compile/pandoc.plugin @@ -5,7 +5,7 @@ module = pandoc [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Compile markups into HTML using pandoc [Nikola] diff --git a/nikola/plugins/compile/pandoc.py b/nikola/plugins/compile/pandoc.py index 3030626..85e84fc 100644 --- a/nikola/plugins/compile/pandoc.py +++ b/nikola/plugins/compile/pandoc.py @@ -40,7 +40,6 @@ from nikola.utils import req_missing, makedirs, write_metadata class CompilePandoc(PageCompiler): - """Compile markups into HTML using pandoc.""" name = "pandoc" diff --git a/nikola/plugins/compile/php.plugin b/nikola/plugins/compile/php.plugin index 151c022..f4fb0c1 100644 --- a/nikola/plugins/compile/php.plugin +++ b/nikola/plugins/compile/php.plugin @@ -5,7 +5,7 @@ module = php [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Compile PHP into HTML (just copy and name the file .php) [Nikola] diff --git a/nikola/plugins/compile/php.py b/nikola/plugins/compile/php.py index 28f4923..a60e31a 100644 --- a/nikola/plugins/compile/php.py +++ b/nikola/plugins/compile/php.py @@ -37,7 +37,6 @@ from hashlib import md5 class CompilePhp(PageCompiler): - """Compile PHP into PHP.""" name = "php" diff --git a/nikola/plugins/compile/rest.plugin b/nikola/plugins/compile/rest.plugin index cf842c7..4d9041a 100644 --- a/nikola/plugins/compile/rest.plugin +++ b/nikola/plugins/compile/rest.plugin @@ -5,7 +5,7 @@ module = rest [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Compile reSt into HTML [Nikola] diff --git a/nikola/plugins/compile/rest/__init__.py b/nikola/plugins/compile/rest/__init__.py index b99e872..4b04958 100644 --- a/nikola/plugins/compile/rest/__init__.py +++ b/nikola/plugins/compile/rest/__init__.py @@ -42,7 +42,6 @@ from nikola.utils import unicode_str, get_logger, makedirs, write_metadata, STDE class CompileRest(PageCompiler): - """Compile reStructuredText into HTML.""" name = "rest" @@ -172,7 +171,6 @@ def get_observer(settings): class NikolaReader(docutils.readers.standalone.Reader): - """Nikola-specific docutils reader.""" def __init__(self, *args, **kwargs): @@ -196,7 +194,7 @@ 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>`_. + `Sphinx add_node function <http://sphinx-doc.org/extdev/appapi.html#sphinx.application.Sphinx.add_node>`_. For example:: diff --git a/nikola/plugins/compile/rest/chart.plugin b/nikola/plugins/compile/rest/chart.plugin index 438abe4..0a7896f 100644 --- a/nikola/plugins/compile/rest/chart.plugin +++ b/nikola/plugins/compile/rest/chart.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Roberto Alsina version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Chart directive based in PyGal diff --git a/nikola/plugins/compile/rest/chart.py b/nikola/plugins/compile/rest/chart.py index 88fdff3..ed8a250 100644 --- a/nikola/plugins/compile/rest/chart.py +++ b/nikola/plugins/compile/rest/chart.py @@ -43,7 +43,6 @@ _site = None class Plugin(RestExtension): - """Plugin for chart role.""" name = "rest_chart" @@ -57,7 +56,6 @@ class Plugin(RestExtension): class Chart(Directive): - """reStructuredText extension for inserting charts as SVG. Usage: @@ -150,15 +148,15 @@ class Chart(Directive): style = getattr(pygal.style, style_name) for k, v in self.options.items(): options[k] = literal_eval(v) - - chart = getattr(pygal, self.arguments[0])(style=style) + chart = pygal + for o in self.arguments[0].split('.'): + chart = getattr(chart, o) + chart = chart(style=style) + if _site and _site.invariant: + chart.no_prefix = True chart.config(**options) for line in self.content: label, series = literal_eval('({0})'.format(line)) chart.add(label, series) data = chart.render().decode('utf8') - if _site and _site.invariant: - import re - data = re.sub('id="chart-[a-f0-9\-]+"', 'id="chart-foobar"', data) - data = re.sub('#chart-[a-f0-9\-]+', '#chart-foobar', data) return [nodes.raw('', data, format='html')] diff --git a/nikola/plugins/compile/rest/doc.plugin b/nikola/plugins/compile/rest/doc.plugin index facdd03..e447eb2 100644 --- a/nikola/plugins/compile/rest/doc.plugin +++ b/nikola/plugins/compile/rest/doc.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Manuel Kaufmann version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Role to link another page / post from the blog diff --git a/nikola/plugins/compile/rest/doc.py b/nikola/plugins/compile/rest/doc.py index 99cce81..578f012 100644 --- a/nikola/plugins/compile/rest/doc.py +++ b/nikola/plugins/compile/rest/doc.py @@ -34,7 +34,6 @@ from nikola.plugin_categories import RestExtension class Plugin(RestExtension): - """Plugin for doc role.""" name = 'rest_doc' diff --git a/nikola/plugins/compile/rest/gist.plugin b/nikola/plugins/compile/rest/gist.plugin index 9fa2e82..763c1d2 100644 --- a/nikola/plugins/compile/rest/gist.plugin +++ b/nikola/plugins/compile/rest/gist.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Roberto Alsina version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Gist directive diff --git a/nikola/plugins/compile/rest/gist.py b/nikola/plugins/compile/rest/gist.py index 736ee37..e40c3b2 100644 --- a/nikola/plugins/compile/rest/gist.py +++ b/nikola/plugins/compile/rest/gist.py @@ -11,7 +11,6 @@ from nikola.plugin_categories import RestExtension class Plugin(RestExtension): - """Plugin for gist directive.""" name = "rest_gist" @@ -24,7 +23,6 @@ class Plugin(RestExtension): class GitHubGist(Directive): - """Embed GitHub Gist. Usage: diff --git a/nikola/plugins/compile/rest/listing.plugin b/nikola/plugins/compile/rest/listing.plugin index 85c780f..3ebb296 100644 --- a/nikola/plugins/compile/rest/listing.plugin +++ b/nikola/plugins/compile/rest/listing.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Roberto Alsina version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Extension for source listings diff --git a/nikola/plugins/compile/rest/listing.py b/nikola/plugins/compile/rest/listing.py index 4871bf3..07f1686 100644 --- a/nikola/plugins/compile/rest/listing.py +++ b/nikola/plugins/compile/rest/listing.py @@ -55,7 +55,6 @@ from nikola.plugin_categories import RestExtension # A sanitized version of docutils.parsers.rst.directives.body.CodeBlock. class CodeBlock(Directive): - """Parse and mark up content of a code block.""" optional_arguments = 1 @@ -126,7 +125,6 @@ docutils.parsers.rst.directives.misc.CodeBlock = CodeBlock class Plugin(RestExtension): - """Plugin for listing directive.""" name = "rest_listing" @@ -152,7 +150,6 @@ listing_spec['linenos'] = directives.unchanged class Listing(Include): - """Create a highlighted block of code from a file in listings/. Usage: @@ -171,7 +168,12 @@ class Listing(Include): """Run listing directive.""" _fname = self.arguments.pop(0) fname = _fname.replace('/', os.sep) - lang = self.arguments.pop(0) + try: + lang = self.arguments.pop(0) + self.options['code'] = lang + except IndexError: + self.options['literal'] = True + if len(self.folders) == 1: listings_folder = next(iter(self.folders.keys())) if fname.startswith(listings_folder): @@ -181,7 +183,6 @@ class Listing(Include): else: fpath = os.path.join(fname) # must be new syntax: specify folder name self.arguments.insert(0, fpath) - self.options['code'] = lang if 'linenos' in self.options: self.options['number-lines'] = self.options['linenos'] with io.open(fpath, 'r+', encoding='utf8') as fileobject: diff --git a/nikola/plugins/compile/rest/media.plugin b/nikola/plugins/compile/rest/media.plugin index 9803c8f..8dfb19c 100644 --- a/nikola/plugins/compile/rest/media.plugin +++ b/nikola/plugins/compile/rest/media.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Roberto Alsina version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Directive to support oembed via micawber diff --git a/nikola/plugins/compile/rest/media.py b/nikola/plugins/compile/rest/media.py index 345e331..d075e44 100644 --- a/nikola/plugins/compile/rest/media.py +++ b/nikola/plugins/compile/rest/media.py @@ -40,7 +40,6 @@ from nikola.utils import req_missing class Plugin(RestExtension): - """Plugin for reST media directive.""" name = "rest_media" @@ -53,7 +52,6 @@ class Plugin(RestExtension): class Media(Directive): - """reST extension for inserting any sort of media using micawber.""" has_content = False diff --git a/nikola/plugins/compile/rest/post_list.plugin b/nikola/plugins/compile/rest/post_list.plugin index 48969bf..1802f2b 100644 --- a/nikola/plugins/compile/rest/post_list.plugin +++ b/nikola/plugins/compile/rest/post_list.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Udo Spallek version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Includes a list of posts with tag and slide based filters. diff --git a/nikola/plugins/compile/rest/post_list.py b/nikola/plugins/compile/rest/post_list.py index a22ee85..df9376b 100644 --- a/nikola/plugins/compile/rest/post_list.py +++ b/nikola/plugins/compile/rest/post_list.py @@ -43,7 +43,6 @@ from nikola.plugin_categories import RestExtension class Plugin(RestExtension): - """Plugin for reST post-list directive.""" name = "rest_post_list" @@ -57,7 +56,6 @@ class Plugin(RestExtension): class PostList(Directive): - """Provide a reStructuredText directive to create a list of posts. Post List @@ -204,8 +202,10 @@ class PostList(Directive): template_data = { 'lang': lang, 'posts': posts, - 'date_format': self.site.GLOBAL_CONTEXT.get('date_format'), + # Need to provide str, not TranslatableSetting (Issue #2104) + 'date_format': self.site.GLOBAL_CONTEXT.get('date_format')[lang], 'post_list_id': post_list_id, + 'messages': self.site.MESSAGES, } output = self.site.template_system.render_template( template, None, template_data) diff --git a/nikola/plugins/compile/rest/slides.plugin b/nikola/plugins/compile/rest/slides.plugin index 5c05b89..389da39 100644 --- a/nikola/plugins/compile/rest/slides.plugin +++ b/nikola/plugins/compile/rest/slides.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Roberto Alsina version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Slides directive diff --git a/nikola/plugins/compile/rest/slides.py b/nikola/plugins/compile/rest/slides.py index 2522e55..eb2cc97 100644 --- a/nikola/plugins/compile/rest/slides.py +++ b/nikola/plugins/compile/rest/slides.py @@ -37,7 +37,6 @@ from nikola.plugin_categories import RestExtension class Plugin(RestExtension): - """Plugin for reST slides directive.""" name = "rest_slides" @@ -51,7 +50,6 @@ class Plugin(RestExtension): class Slides(Directive): - """reST extension for inserting slideshows.""" has_content = True diff --git a/nikola/plugins/compile/rest/soundcloud.plugin b/nikola/plugins/compile/rest/soundcloud.plugin index 75469e4..4e36ea4 100644 --- a/nikola/plugins/compile/rest/soundcloud.plugin +++ b/nikola/plugins/compile/rest/soundcloud.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Roberto Alsina version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = Soundcloud directive diff --git a/nikola/plugins/compile/rest/soundcloud.py b/nikola/plugins/compile/rest/soundcloud.py index 30134a9..2577ff1 100644 --- a/nikola/plugins/compile/rest/soundcloud.py +++ b/nikola/plugins/compile/rest/soundcloud.py @@ -10,7 +10,6 @@ from nikola.plugin_categories import RestExtension class Plugin(RestExtension): - """Plugin for soundclound directive.""" name = "rest_soundcloud" @@ -31,7 +30,6 @@ src="https://w.soundcloud.com/player/?url=http://api.soundcloud.com/{preslug}/"" class SoundCloud(Directive): - """reST extension for inserting SoundCloud embedded music. Usage: @@ -70,7 +68,6 @@ class SoundCloud(Directive): class SoundCloudPlaylist(SoundCloud): - """reST directive for SoundCloud playlists.""" preslug = "playlists" diff --git a/nikola/plugins/compile/rest/thumbnail.plugin b/nikola/plugins/compile/rest/thumbnail.plugin index 0084310..3324c31 100644 --- a/nikola/plugins/compile/rest/thumbnail.plugin +++ b/nikola/plugins/compile/rest/thumbnail.plugin @@ -9,6 +9,6 @@ plugincategory = CompilerExtension [Documentation] author = Pelle Nilsson version = 0.1 -website = http://getnikola.com +website = https://getnikola.com/ description = reST directive to facilitate enlargeable images with thumbnails diff --git a/nikola/plugins/compile/rest/thumbnail.py b/nikola/plugins/compile/rest/thumbnail.py index 1fae06c..c24134a 100644 --- a/nikola/plugins/compile/rest/thumbnail.py +++ b/nikola/plugins/compile/rest/thumbnail.py @@ -35,7 +35,6 @@ from nikola.plugin_categories import RestExtension class Plugin(RestExtension): - """Plugin for thumbnail directive.""" name = "rest_thumbnail" @@ -48,7 +47,6 @@ class Plugin(RestExtension): class Thumbnail(Figure): - """Thumbnail directive for reST.""" def align(argument): @@ -70,8 +68,12 @@ class Thumbnail(Figure): def run(self): """Run the thumbnail directive.""" uri = directives.uri(self.arguments[0]) + if uri.endswith('.svg'): + # the ? at the end makes docutil output an <img> instead of an object for the svg, which colorbox requires + self.arguments[0] = '.thumbnail'.join(os.path.splitext(uri)) + '?' + else: + self.arguments[0] = '.thumbnail'.join(os.path.splitext(uri)) self.options['target'] = uri - self.arguments[0] = '.thumbnail'.join(os.path.splitext(uri)) if self.content: (node,) = Figure.run(self) else: diff --git a/nikola/plugins/compile/rest/vimeo.py b/nikola/plugins/compile/rest/vimeo.py index c694a87..29ce5c1 100644 --- a/nikola/plugins/compile/rest/vimeo.py +++ b/nikola/plugins/compile/rest/vimeo.py @@ -37,7 +37,6 @@ from nikola.plugin_categories import RestExtension class Plugin(RestExtension): - """Plugin for vimeo reST directive.""" name = "rest_vimeo" @@ -60,7 +59,6 @@ VIDEO_DEFAULT_WIDTH = 281 class Vimeo(Directive): - """reST extension for inserting vimeo embedded videos. Usage: diff --git a/nikola/plugins/compile/rest/youtube.py b/nikola/plugins/compile/rest/youtube.py index 6c5c211..b3b84b0 100644 --- a/nikola/plugins/compile/rest/youtube.py +++ b/nikola/plugins/compile/rest/youtube.py @@ -34,7 +34,6 @@ from nikola.plugin_categories import RestExtension class Plugin(RestExtension): - """Plugin for the youtube directive.""" name = "rest_youtube" @@ -54,7 +53,6 @@ src="//www.youtube.com/embed/{yid}?rel=0&hd=1&wmode=transparent" class Youtube(Directive): - """reST extension for inserting youtube embedded videos. Usage: diff --git a/nikola/plugins/misc/scan_posts.plugin b/nikola/plugins/misc/scan_posts.plugin index 6d2351f..f4af811 100644 --- a/nikola/plugins/misc/scan_posts.plugin +++ b/nikola/plugins/misc/scan_posts.plugin @@ -5,6 +5,6 @@ Module = scan_posts [Documentation] Author = Roberto Alsina Version = 1.0 -Website = http://getnikola.com +Website = https://getnikola.com/ Description = Scan posts and create timeline diff --git a/nikola/plugins/misc/scan_posts.py b/nikola/plugins/misc/scan_posts.py index 1f4f995..9db4533 100644 --- a/nikola/plugins/misc/scan_posts.py +++ b/nikola/plugins/misc/scan_posts.py @@ -37,7 +37,6 @@ from nikola.post import Post class ScanPosts(PostScanner): - """Scan posts in the site.""" name = "scan_posts" diff --git a/nikola/plugins/task/archive.plugin b/nikola/plugins/task/archive.plugin index 25f1195..eb079da 100644 --- a/nikola/plugins/task/archive.plugin +++ b/nikola/plugins/task/archive.plugin @@ -5,7 +5,7 @@ module = archive [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Generates the blog's archive pages. [Nikola] diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 126aed4..3cdd33b 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -37,7 +37,6 @@ from nikola.utils import config_changed, adjust_name_for_index_path, adjust_name class Archive(Task): - """Render the post archives.""" name = "render_archive" @@ -53,7 +52,7 @@ class Archive(Task): """Prepare an archive task.""" # name: used to build permalink and destination # posts, items: posts or items; only one of them should be used, - # the other be None + # the other should be None # template_name: name of the template to use # title: the (translated) title for the generated page # deps_translatable: dependencies (None if not added) @@ -175,10 +174,10 @@ class Archive(Task): if not kw["create_monthly_archive"] or kw["create_full_archives"]: yield self._generate_posts_task(kw, year, lang, posts, title, deps_translatable) else: - months = set([(m.split('/')[1], self.site.link("archive", m, lang)) for m in self.site.posts_per_month.keys() if m.startswith(str(year))]) + months = set([(m.split('/')[1], self.site.link("archive", m, lang), len(self.site.posts_per_month[m])) for m in self.site.posts_per_month.keys() if m.startswith(str(year))]) months = sorted(list(months)) months.reverse() - items = [[nikola.utils.LocaleBorg().get_month_name(int(month), lang), link] for month, link in months] + items = [[nikola.utils.LocaleBorg().get_month_name(int(month), lang), link, count] for month, link, count in months] yield self._prepare_task(kw, year, lang, None, items, "list.tmpl", title, deps_translatable) if not kw["create_monthly_archive"] and not kw["create_full_archives"] and not kw["create_daily_archive"]: @@ -219,11 +218,16 @@ class Archive(Task): years.sort(reverse=True) kw['years'] = years for lang in kw["translations"]: - items = [(y, self.site.link("archive", y, lang)) for y in years] + items = [(y, self.site.link("archive", y, lang), len(self.site.posts_per_year[y])) for y in years] yield self._prepare_task(kw, None, lang, None, items, "list.tmpl", kw["messages"][lang]["Archive"]) def archive_path(self, name, lang, is_feed=False): - """Return archive paths.""" + """Link to archive path, name is the year. + + Example: + + link://archive/2013 => /archives/2013/index.html + """ if is_feed: extension = ".atom" archive_file = os.path.splitext(self.site.config['ARCHIVE_FILENAME'])[0] + extension @@ -241,5 +245,10 @@ class Archive(Task): archive_file] if _f] def archive_atom_path(self, name, lang): - """Return Atom archive paths.""" + """Link to atom archive path, name is the year. + + Example: + + link://archive_atom/2013 => /archives/2013/index.atom + """ return self.archive_path(name, lang, is_feed=True) diff --git a/nikola/plugins/task/authors.plugin b/nikola/plugins/task/authors.plugin new file mode 100644 index 0000000..3fc4ef2 --- /dev/null +++ b/nikola/plugins/task/authors.plugin @@ -0,0 +1,10 @@ +[Core] +Name = render_authors +Module = authors + +[Documentation] +Author = Juanjo Conti +Version = 0.1 +Website = http://getnikola.com +Description = Render the author pages and feeds. + diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py new file mode 100644 index 0000000..081d21d --- /dev/null +++ b/nikola/plugins/task/authors.py @@ -0,0 +1,316 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2015 Juanjo Conti. + +# 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. + +"""Render the author pages and feeds.""" + +from __future__ import unicode_literals +import os +import natsort +try: + from urlparse import urljoin +except ImportError: + from urllib.parse import urljoin # NOQA +from collections import defaultdict + +from nikola.plugin_categories import Task +from nikola import utils + + +class RenderAuthors(Task): + """Render the author pages and feeds.""" + + name = "render_authors" + posts_per_author = None + + def set_site(self, site): + """Set Nikola site.""" + if site.config["ENABLE_AUTHOR_PAGES"]: + site.register_path_handler('author_index', self.author_index_path) + site.register_path_handler('author', self.author_path) + site.register_path_handler('author_atom', self.author_atom_path) + site.register_path_handler('author_rss', self.author_rss_path) + return super(RenderAuthors, self).set_site(site) + + def gen_tasks(self): + """Render the author pages and feeds.""" + kw = { + "translations": self.site.config["TRANSLATIONS"], + "blog_title": self.site.config["BLOG_TITLE"], + "site_url": self.site.config["SITE_URL"], + "base_url": self.site.config["BASE_URL"], + "messages": self.site.MESSAGES, + "output_folder": self.site.config['OUTPUT_FOLDER'], + "filters": self.site.config['FILTERS'], + 'author_path': self.site.config['AUTHOR_PATH'], + "author_pages_are_indexes": self.site.config['AUTHOR_PAGES_ARE_INDEXES'], + "generate_rss": self.site.config['GENERATE_RSS'], + "feed_teasers": self.site.config["FEED_TEASERS"], + "feed_plain": self.site.config["FEED_PLAIN"], + "feed_link_append_query": self.site.config["FEED_LINKS_APPEND_QUERY"], + "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], + "feed_length": self.site.config['FEED_LENGTH'], + "tzinfo": self.site.tzinfo, + "pretty_urls": self.site.config['PRETTY_URLS'], + "strip_indexes": self.site.config['STRIP_INDEXES'], + "index_file": self.site.config['INDEX_FILE'], + } + + yield self.group_task() + self.site.scan_posts() + + generate_author_pages = self.site.config["ENABLE_AUTHOR_PAGES"] and len(self._posts_per_author()) > 1 + self.site.GLOBAL_CONTEXT["author_pages_generated"] = generate_author_pages + if generate_author_pages: + yield self.list_authors_page(kw) + + if not self._posts_per_author(): # this may be self.site.posts_per_author + return + + author_list = list(self._posts_per_author().items()) + + def render_lists(author, posts): + """Render author pages as RSS files and lists/indexes.""" + post_list = sorted(posts, key=lambda a: a.date) + post_list.reverse() + for lang in kw["translations"]: + if kw["show_untranslated_posts"]: + filtered_posts = post_list + else: + filtered_posts = [x for x in post_list if x.is_translation_available(lang)] + if kw["generate_rss"]: + yield self.author_rss(author, lang, filtered_posts, kw) + # Render HTML + if kw['author_pages_are_indexes']: + yield self.author_page_as_index(author, lang, filtered_posts, kw) + else: + yield self.author_page_as_list(author, lang, filtered_posts, kw) + + for author, posts in author_list: + for task in render_lists(author, posts): + yield task + + def _create_authors_page(self, kw): + """Create a global "all authors" page for each language.""" + template_name = "authors.tmpl" + kw = kw.copy() + for lang in kw["translations"]: + authors = natsort.natsorted([author for author in self._posts_per_author().keys()], + alg=natsort.ns.F | natsort.ns.IC) + has_authors = (authors != []) + kw['authors'] = authors + output_name = os.path.join( + kw['output_folder'], self.site.path('author_index', None, lang)) + context = {} + if has_authors: + context["title"] = kw["messages"][lang]["Authors"] + context["items"] = [(author, self.site.link("author", author, lang)) for author + in authors] + context["description"] = context["title"] + else: + context["items"] = None + context["permalink"] = self.site.link("author_index", None, lang) + context["pagekind"] = ["list", "authors_page"] + task = self.site.generic_post_list_renderer( + lang, + [], + output_name, + template_name, + kw['filters'], + context, + ) + task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.authors:page')] + task['basename'] = str(self.name) + yield task + + def list_authors_page(self, kw): + """Create a global "all authors" page for each language.""" + yield self._create_authors_page(kw) + + def _get_title(self, author): + return author + + def _get_description(self, author, lang): + descriptions = self.site.config['AUTHOR_PAGES_DESCRIPTIONS'] + return descriptions[lang][author] if lang in descriptions and author in descriptions[lang] else None + + def author_page_as_index(self, author, lang, post_list, kw): + """Render a sort of index page collection using only this author's posts.""" + kind = "author" + + def page_link(i, displayed_i, num_pages, force_addition, extension=None): + feed = "_atom" if extension == ".atom" else "" + return utils.adjust_name_for_index_link(self.site.link(kind + feed, author, lang), i, displayed_i, lang, self.site, force_addition, extension) + + def page_path(i, displayed_i, num_pages, force_addition, extension=None): + feed = "_atom" if extension == ".atom" else "" + return utils.adjust_name_for_index_path(self.site.path(kind + feed, author, lang), i, displayed_i, lang, self.site, force_addition, extension) + + context_source = {} + title = self._get_title(author) + if kw["generate_rss"]: + # On a author page, the feeds include the author's feeds + rss_link = ("""<link rel="alternate" type="application/rss+xml" """ + """title="RSS for author """ + """{0} ({1})" href="{2}">""".format( + title, lang, self.site.link(kind + "_rss", author, lang))) + context_source['rss_link'] = rss_link + context_source["author"] = title + indexes_title = kw["messages"][lang]["Posts by %s"] % title + context_source["description"] = self._get_description(author, lang) + context_source["pagekind"] = ["index", "author_page"] + template_name = "authorindex.tmpl" + + yield self.site.generic_index_renderer(lang, post_list, indexes_title, template_name, context_source, kw, str(self.name), page_link, page_path) + + def author_page_as_list(self, author, lang, post_list, kw): + """Render a single flat link list with this author's posts.""" + kind = "author" + template_name = "author.tmpl" + output_name = os.path.join(kw['output_folder'], self.site.path( + kind, author, lang)) + context = {} + context["lang"] = lang + title = self._get_title(author) + context["author"] = title + context["title"] = kw["messages"][lang]["Posts by %s"] % title + context["posts"] = post_list + context["permalink"] = self.site.link(kind, author, lang) + context["kind"] = kind + context["description"] = self._get_description(author, lang) + context["pagekind"] = ["list", "author_page"] + task = self.site.generic_post_list_renderer( + lang, + post_list, + output_name, + template_name, + kw['filters'], + context, + ) + task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.authors:list')] + task['basename'] = str(self.name) + yield task + + def author_rss(self, author, lang, posts, kw): + """Create a RSS feed for a single author in a given language.""" + kind = "author" + # Render RSS + output_name = os.path.normpath( + os.path.join(kw['output_folder'], + self.site.path(kind + "_rss", author, lang))) + feed_url = urljoin(self.site.config['BASE_URL'], self.site.link(kind + "_rss", author, lang).lstrip('/')) + deps = [] + deps_uptodate = [] + post_list = sorted(posts, key=lambda a: a.date) + post_list.reverse() + for post in post_list: + deps += post.deps(lang) + deps_uptodate += post.deps_uptodate(lang) + task = { + 'basename': str(self.name), + 'name': output_name, + 'file_dep': deps, + 'targets': [output_name], + 'actions': [(utils.generic_rss_renderer, + (lang, "{0} ({1})".format(kw["blog_title"](lang), self._get_title(author)), + kw["site_url"], None, post_list, + output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], + feed_url, None, kw["feed_link_append_query"]))], + 'clean': True, + 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.authors:rss')] + deps_uptodate, + 'task_dep': ['render_posts'], + } + return utils.apply_filters(task, kw['filters']) + + def slugify_author_name(self, name): + """Slugify an author name.""" + if self.site.config['SLUG_AUTHOR_PATH']: + name = utils.slugify(name) + return name + + def author_index_path(self, name, lang): + """Link to the author's index. + + Example: + + link://authors/ => /authors/index.html + """ + return [_f for _f in [self.site.config['TRANSLATIONS'][lang], + self.site.config['AUTHOR_PATH'], + self.site.config['INDEX_FILE']] if _f] + + def author_path(self, name, lang): + """Link to an author's page. + + Example: + + link://author/joe => /authors/joe.html + """ + if self.site.config['PRETTY_URLS']: + return [_f for _f in [ + self.site.config['TRANSLATIONS'][lang], + self.site.config['AUTHOR_PATH'], + self.slugify_author_name(name), + self.site.config['INDEX_FILE']] if _f] + else: + return [_f for _f in [ + self.site.config['TRANSLATIONS'][lang], + self.site.config['AUTHOR_PATH'], + self.slugify_author_name(name) + ".html"] if _f] + + def author_atom_path(self, name, lang): + """Link to an author's Atom feed. + + Example: + + link://author_atom/joe => /authors/joe.atom + """ + return [_f for _f in [self.site.config['TRANSLATIONS'][lang], + self.site.config['AUTHOR_PATH'], self.slugify_author_name(name) + ".atom"] if + _f] + + def author_rss_path(self, name, lang): + """Link to an author's RSS feed. + + Example: + + link://author_rss/joe => /authors/joe.rss + """ + return [_f for _f in [self.site.config['TRANSLATIONS'][lang], + self.site.config['AUTHOR_PATH'], self.slugify_author_name(name) + ".xml"] if + _f] + + def _add_extension(self, path, extension): + path[-1] += extension + return path + + def _posts_per_author(self): + """Return a dict of posts per author.""" + if self.posts_per_author is None: + self.posts_per_author = defaultdict(list) + for post in self.site.timeline: + if post.is_post: + self.posts_per_author[post.author()].append(post) + return self.posts_per_author diff --git a/nikola/plugins/task/bundles.plugin b/nikola/plugins/task/bundles.plugin index ca997d0..b5bf6e4 100644 --- a/nikola/plugins/task/bundles.plugin +++ b/nikola/plugins/task/bundles.plugin @@ -5,7 +5,7 @@ module = bundles [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Theme bundles using WebAssets [Nikola] diff --git a/nikola/plugins/task/bundles.py b/nikola/plugins/task/bundles.py index b9c57b9..e709133 100644 --- a/nikola/plugins/task/bundles.py +++ b/nikola/plugins/task/bundles.py @@ -40,7 +40,6 @@ from nikola import utils class BuildBundles(LateTask): - """Bundle assets using WebAssets.""" name = "create_bundles" @@ -52,6 +51,7 @@ class BuildBundles(LateTask): utils.req_missing(['webassets'], 'USE_BUNDLES', optional=True) self.logger.warn('Setting USE_BUNDLES to False.') site.config['USE_BUNDLES'] = False + site._GLOBAL_CONTEXT['use_bundles'] = False super(BuildBundles, self).set_site(site) def gen_tasks(self): @@ -100,7 +100,11 @@ class BuildBundles(LateTask): 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']) or fname == os.path.join('assets', 'css', 'code.css')] + 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. diff --git a/nikola/plugins/task/copy_assets.plugin b/nikola/plugins/task/copy_assets.plugin index c182150..ddd38df 100644 --- a/nikola/plugins/task/copy_assets.plugin +++ b/nikola/plugins/task/copy_assets.plugin @@ -5,7 +5,7 @@ module = copy_assets [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Copy theme assets into output. [Nikola] diff --git a/nikola/plugins/task/copy_assets.py b/nikola/plugins/task/copy_assets.py index 58521d4..2cab71a 100644 --- a/nikola/plugins/task/copy_assets.py +++ b/nikola/plugins/task/copy_assets.py @@ -36,7 +36,6 @@ from nikola import utils class CopyAssets(Task): - """Copy theme assets into output.""" name = "copy_assets" @@ -61,10 +60,7 @@ class CopyAssets(Task): 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']) - - kw["code.css_input"] = code_css_input - + files_folders=kw['files_folders'], output_dir=None) yield self.group_task() for theme_name in kw['themes']: @@ -77,7 +73,9 @@ class CopyAssets(Task): task['uptodate'] = [utils.config_changed(kw, 'nikola.plugins.task.copy_assets')] task['basename'] = self.name if code_css_input: - task['file_dep'] = [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. diff --git a/nikola/plugins/task/copy_files.plugin b/nikola/plugins/task/copy_files.plugin index ce8f5d0..e4bb1cf 100644 --- a/nikola/plugins/task/copy_files.plugin +++ b/nikola/plugins/task/copy_files.plugin @@ -5,7 +5,7 @@ module = copy_files [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Copy static files into the output. [Nikola] diff --git a/nikola/plugins/task/copy_files.py b/nikola/plugins/task/copy_files.py index 1232248..0488011 100644 --- a/nikola/plugins/task/copy_files.py +++ b/nikola/plugins/task/copy_files.py @@ -33,7 +33,6 @@ from nikola import utils class CopyFiles(Task): - """Copy static files into the output folder.""" name = "copy_files" diff --git a/nikola/plugins/task/galleries.plugin b/nikola/plugins/task/galleries.plugin index 9d3fa28..2064e68 100644 --- a/nikola/plugins/task/galleries.plugin +++ b/nikola/plugins/task/galleries.plugin @@ -5,7 +5,7 @@ module = galleries [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create image galleries automatically. [Nikola] diff --git a/nikola/plugins/task/galleries.py b/nikola/plugins/task/galleries.py index c0df4a4..d3f1db7 100644 --- a/nikola/plugins/task/galleries.py +++ b/nikola/plugins/task/galleries.py @@ -57,7 +57,6 @@ _image_size_cache = {} class Galleries(Task, ImageProcessor): - """Render image galleries.""" name = 'render_galleries' @@ -122,20 +121,45 @@ class Galleries(Task, ImageProcessor): sys.exit(1) def gallery_path(self, name, lang): - """Return a gallery path.""" + """Link to an image gallery's path. + + It will try to find a gallery with that name if it's not ambiguous + or with that path. For example: + + link://gallery/london => /galleries/trips/london/index.html + + link://gallery/trips/london => /galleries/trips/london/index.html + """ gallery_path = self._find_gallery_path(name) return [_f for _f in [self.site.config['TRANSLATIONS'][lang]] + gallery_path.split(os.sep) + [self.site.config['INDEX_FILE']] if _f] def gallery_global_path(self, name, lang): - """Return the global gallery path, which contains images.""" + """Link to the global gallery path, which contains all the images in galleries. + + There is only one copy of an image on multilingual blogs, in the site root. + + link://gallery_global/london => /galleries/trips/london/index.html + + link://gallery_global/trips/london => /galleries/trips/london/index.html + + (a ``gallery`` link could lead to eg. /en/galleries/trips/london/index.html) + """ gallery_path = self._find_gallery_path(name) return [_f for _f in gallery_path.split(os.sep) + [self.site.config['INDEX_FILE']] if _f] def gallery_rss_path(self, name, lang): - """Return path to the RSS file for a gallery.""" + """Link to an image gallery's RSS feed. + + It will try to find a gallery with that name if it's not ambiguous + or with that path. For example: + + link://gallery_rss/london => /galleries/trips/london/rss.xml + + link://gallery_rss/trips/london => /galleries/trips/london/rss.xml + """ gallery_path = self._find_gallery_path(name) return [_f for _f in [self.site.config['TRANSLATIONS'][lang]] + gallery_path.split(os.sep) + @@ -538,9 +562,12 @@ class Galleries(Task, ImageProcessor): for img, thumb, title in zip(img_list, thumbs, img_titles): w, h = _image_size_cache.get(thumb, (None, None)) if w is None: - im = Image.open(thumb) - w, h = im.size - _image_size_cache[thumb] = w, h + if os.path.splitext(thumb)[1] in ['.svg', '.svgz']: + w, h = 200, 200 + else: + im = Image.open(thumb) + w, h = im.size + _image_size_cache[thumb] = w, h # Thumbs are files in output, we need URLs photo_array.append({ 'url': url_from_path(img), @@ -587,7 +614,7 @@ class Galleries(Task, ImageProcessor): description='', lastBuildDate=datetime.datetime.utcnow(), items=items, - generator='http://getnikola.com/', + generator='https://getnikola.com/', language=lang ) diff --git a/nikola/plugins/task/gzip.plugin b/nikola/plugins/task/gzip.plugin index 7834d22..d3a34ee 100644 --- a/nikola/plugins/task/gzip.plugin +++ b/nikola/plugins/task/gzip.plugin @@ -5,7 +5,7 @@ module = gzip [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create gzipped copies of files [Nikola] diff --git a/nikola/plugins/task/gzip.py b/nikola/plugins/task/gzip.py index cf16f63..aaa213d 100644 --- a/nikola/plugins/task/gzip.py +++ b/nikola/plugins/task/gzip.py @@ -35,7 +35,6 @@ from nikola.plugin_categories import TaskMultiplier class GzipFiles(TaskMultiplier): - """If appropiate, create tasks to create gzipped versions of files.""" name = "gzip" diff --git a/nikola/plugins/task/indexes.plugin b/nikola/plugins/task/indexes.plugin index d9b0e5f..553b5ad 100644 --- a/nikola/plugins/task/indexes.plugin +++ b/nikola/plugins/task/indexes.plugin @@ -5,7 +5,7 @@ module = indexes [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Generates the blog's index pages. [Nikola] diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index c02818e..2ab97fa 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -29,23 +29,45 @@ from __future__ import unicode_literals from collections import defaultdict import os +try: + from urlparse import urljoin +except ImportError: + from urllib.parse import urljoin # NOQA from nikola.plugin_categories import Task from nikola import utils class Indexes(Task): - """Render the blog indexes.""" name = "render_indexes" def set_site(self, site): """Set Nikola site.""" + self.number_of_pages = dict() + self.number_of_pages_section = {lang: dict() for lang in site.config['TRANSLATIONS']} site.register_path_handler('index', self.index_path) site.register_path_handler('index_atom', self.index_atom_path) + site.register_path_handler('section_index', self.index_section_path) + site.register_path_handler('section_index_atom', self.index_section_atom_path) return super(Indexes, self).set_site(site) + def _get_filtered_posts(self, lang, show_untranslated_posts): + """Return a filtered list of all posts for the given language. + + If show_untranslated_posts is True, will only include posts which + are translated to the given language. Otherwise, returns all posts. + """ + if show_untranslated_posts: + return self.site.posts + else: + return [x for x in self.site.posts if x.is_translation_available(lang)] + + def _compute_number_of_pages(self, filtered_posts, posts_count): + """Given a list of posts and the maximal number of posts per page, computes the number of pages needed.""" + return min(1, (len(filtered_posts) + posts_count - 1) // posts_count) + def gen_tasks(self): """Render the blog indexes.""" self.site.scan_posts() @@ -56,16 +78,16 @@ class Indexes(Task): "messages": self.site.MESSAGES, "output_folder": self.site.config['OUTPUT_FOLDER'], "filters": self.site.config['FILTERS'], + "index_file": self.site.config['INDEX_FILE'], "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], "index_display_post_count": self.site.config['INDEX_DISPLAY_POST_COUNT'], "indexes_title": self.site.config['INDEXES_TITLE'], + "strip_indexes": self.site.config['STRIP_INDEXES'], "blog_title": self.site.config["BLOG_TITLE"], "generate_atom": self.site.config["GENERATE_ATOM"], } template_name = "index.tmpl" - posts = self.site.posts - self.number_of_pages = dict() for lang in kw["translations"]: def page_link(i, displayed_i, num_pages, force_addition, extension=None): feed = "_atom" if extension == ".atom" else "" @@ -77,19 +99,75 @@ class Indexes(Task): return utils.adjust_name_for_index_path(self.site.path("index" + feed, None, lang), i, displayed_i, lang, self.site, force_addition, extension) - if kw["show_untranslated_posts"]: - filtered_posts = posts - else: - filtered_posts = [x for x in posts if x.is_translation_available(lang)] + filtered_posts = self._get_filtered_posts(lang, kw["show_untranslated_posts"]) indexes_title = kw['indexes_title'](lang) or kw['blog_title'](lang) - self.number_of_pages[lang] = (len(filtered_posts) + kw['index_display_post_count'] - 1) // kw['index_display_post_count'] + self.number_of_pages[lang] = self._compute_number_of_pages(filtered_posts, kw['index_display_post_count']) context = {} - context["pagekind"] = ["index"] + context["pagekind"] = ["main_index", "index"] yield self.site.generic_index_renderer(lang, filtered_posts, indexes_title, template_name, context, kw, 'render_indexes', page_link, page_path) + if self.site.config['POSTS_SECTIONS']: + + kw["posts_section_are_indexes"] = self.site.config['POSTS_SECTION_ARE_INDEXES'] + index_len = len(kw['index_file']) + + groups = defaultdict(list) + for p in filtered_posts: + groups[p.section_slug(lang)].append(p) + + # don't build sections when there is only one, aka. default setups + if not len(groups.items()) > 1: + continue + + for section_slug, post_list in groups.items(): + self.number_of_pages_section[lang][section_slug] = self._compute_number_of_pages(post_list, kw['index_display_post_count']) + + def cat_link(i, displayed_i, num_pages, force_addition, extension=None): + feed = "_atom" if extension == ".atom" else "" + return utils.adjust_name_for_index_link(self.site.link("section_index" + feed, section_slug, lang), i, displayed_i, + lang, self.site, force_addition, extension) + + def cat_path(i, displayed_i, num_pages, force_addition, extension=None): + feed = "_atom" if extension == ".atom" else "" + return utils.adjust_name_for_index_path(self.site.path("section_index" + feed, section_slug, lang), i, displayed_i, + lang, self.site, force_addition, extension) + + context = {} + + short_destination = os.path.join(section_slug, kw['index_file']) + link = short_destination.replace('\\', '/') + if kw['strip_indexes'] and link[-(1 + index_len):] == '/' + kw['index_file']: + link = link[:-index_len] + context["permalink"] = link + context["pagekind"] = ["section_page"] + context["description"] = self.site.config['POSTS_SECTION_DESCRIPTIONS'](lang)[section_slug] if section_slug in self.site.config['POSTS_SECTION_DESCRIPTIONS'](lang) else "" + + if kw["posts_section_are_indexes"]: + context["pagekind"].append("index") + kw["posts_section_title"] = self.site.config['POSTS_SECTION_TITLE'](lang) + + section_title = None + if type(kw["posts_section_title"]) is dict: + if section_slug in kw["posts_section_title"]: + section_title = kw["posts_section_title"][section_slug] + elif type(kw["posts_section_title"]) is str: + section_title = kw["posts_section_title"] + if not section_title: + section_title = post_list[0].section_name(lang) + section_title = section_title.format(name=post_list[0].section_name(lang)) + + task = self.site.generic_index_renderer(lang, post_list, section_title, "sectionindex.tmpl", context, kw, self.name, cat_link, cat_path) + else: + context["pagekind"].append("list") + output_name = os.path.join(kw['output_folder'], section_slug, kw['index_file']) + task = self.site.generic_post_list_renderer(lang, post_list, output_name, "list.tmpl", kw['filters'], context) + task['uptodate'] = [utils.config_changed(kw, 'nikola.plugins.task.indexes')] + task['basename'] = self.name + yield task + if not self.site.config["STORY_INDEX"]: return kw = { @@ -134,7 +212,8 @@ class Indexes(Task): should_render = False else: context["items"].append((post.title(lang), - post.permalink(lang))) + post.permalink(lang), + None)) if should_render: task = self.site.generic_post_list_renderer(lang, post_list, @@ -147,22 +226,75 @@ class Indexes(Task): yield task def index_path(self, name, lang, is_feed=False): - """Return path to an index.""" + """Link to a numbered index. + + Example: + + link://index/3 => /index-3.html + """ extension = None if is_feed: extension = ".atom" index_file = os.path.splitext(self.site.config['INDEX_FILE'])[0] + extension else: index_file = self.site.config['INDEX_FILE'] + if lang in self.number_of_pages: + number_of_pages = self.number_of_pages[lang] + else: + number_of_pages = self._compute_number_of_pages(self._get_filtered_posts(lang, self.site.config['SHOW_UNTRANSLATED_POSTS']), self.site.config['INDEX_DISPLAY_POST_COUNT']) + self.number_of_pages[lang] = number_of_pages return utils.adjust_name_for_index_path_list([_f for _f in [self.site.config['TRANSLATIONS'][lang], self.site.config['INDEX_PATH'], index_file] if _f], name, - utils.get_displayed_page_number(name, self.number_of_pages[lang], self.site), + utils.get_displayed_page_number(name, number_of_pages, self.site), + lang, + self.site, + extension=extension) + + def index_section_path(self, name, lang, is_feed=False): + """Link to the index for a section. + + Example: + + link://section_index/cars => /cars/index.html + """ + extension = None + + if is_feed: + extension = ".atom" + index_file = os.path.splitext(self.site.config['INDEX_FILE'])[0] + extension + else: + index_file = self.site.config['INDEX_FILE'] + if name in self.number_of_pages_section[lang]: + number_of_pages = self.number_of_pages_section[lang][name] + else: + posts = [post for post in self._get_filtered_posts(lang, self.site.config['SHOW_UNTRANSLATED_POSTS']) if post.section_slug(lang) == name] + number_of_pages = self._compute_number_of_pages(posts, self.site.config['INDEX_DISPLAY_POST_COUNT']) + self.number_of_pages_section[lang][name] = number_of_pages + return utils.adjust_name_for_index_path_list([_f for _f in [self.site.config['TRANSLATIONS'][lang], + name, + index_file] if _f], + None, + utils.get_displayed_page_number(None, number_of_pages, self.site), lang, self.site, extension=extension) def index_atom_path(self, name, lang): - """Return path to an Atom index.""" + """Link to a numbered Atom index. + + Example: + + link://index_atom/3 => /index-3.atom + """ return self.index_path(name, lang, is_feed=True) + + def index_section_atom_path(self, name, lang): + """Link to the Atom index for a section. + + Example: + + link://section_index_atom/cars => /cars/index.atom + """ + return self.index_section_path(name, lang, is_feed=True) diff --git a/nikola/plugins/task/listings.plugin b/nikola/plugins/task/listings.plugin index 435234b..8fc2e2d 100644 --- a/nikola/plugins/task/listings.plugin +++ b/nikola/plugins/task/listings.plugin @@ -5,7 +5,7 @@ module = listings [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Render code listings into output [Nikola] diff --git a/nikola/plugins/task/listings.py b/nikola/plugins/task/listings.py index 5f79724..891f361 100644 --- a/nikola/plugins/task/listings.py +++ b/nikola/plugins/task/listings.py @@ -28,6 +28,7 @@ from __future__ import unicode_literals, print_function +from collections import defaultdict import sys import os import lxml.html @@ -41,16 +42,13 @@ from nikola import utils class Listings(Task): - """Render code listings.""" name = "render_listings" def register_output_name(self, input_folder, rel_name, rel_output_name): """Register proper and improper file mappings.""" - if rel_name not in self.improper_input_file_mapping: - self.improper_input_file_mapping[rel_name] = [] - self.improper_input_file_mapping[rel_name].append(rel_output_name) + self.improper_input_file_mapping[rel_name].add(rel_output_name) self.proper_input_file_mapping[os.path.join(input_folder, rel_name)] = rel_output_name self.proper_input_file_mapping[rel_output_name] = rel_output_name @@ -85,7 +83,7 @@ class Listings(Task): # a list is needed. This is needed for compatibility to previous Nikola # versions, where there was no need to specify the input directory name # when asking for a link via site.link('listing', ...). - self.improper_input_file_mapping = {} + self.improper_input_file_mapping = defaultdict(set) # proper_input_file_mapping maps relative input file (relative to CWD) # to a generated output file. Since we don't allow an input directory @@ -255,7 +253,16 @@ class Listings(Task): }, self.kw["filters"]) def listing_path(self, namep, lang): - """Return path to a listing.""" + """A link to a listing. + + It will try to use the file name if it's not ambiguous, or the file path. + + Example: + + link://listing/hello.py => /listings/tutorial/hello.py.html + + link://listing/tutorial/hello.py => /listings/tutorial/hello.py.html + """ namep = namep.replace('/', os.sep) nameh = namep + '.html' for name in (namep, nameh): @@ -271,7 +278,7 @@ class Listings(Task): sys.exit(1) if len(self.site.config['LISTINGS_FOLDERS']) > 1: utils.LOGGER.notice("Using listings names in site.link() without input directory prefix while configuration's LISTINGS_FOLDERS has more than one entry.") - name = self.improper_input_file_mapping[name][0] + name = list(self.improper_input_file_mapping[name])[0] break else: utils.LOGGER.error("Unknown listing name {0}!".format(namep)) diff --git a/nikola/plugins/task/pages.plugin b/nikola/plugins/task/pages.plugin index 023d41b..1bdc7f4 100644 --- a/nikola/plugins/task/pages.plugin +++ b/nikola/plugins/task/pages.plugin @@ -5,7 +5,7 @@ module = pages [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create pages in the output. [Nikola] diff --git a/nikola/plugins/task/pages.py b/nikola/plugins/task/pages.py index e6a8a82..8d41035 100644 --- a/nikola/plugins/task/pages.py +++ b/nikola/plugins/task/pages.py @@ -32,7 +32,6 @@ from nikola.utils import config_changed class RenderPages(Task): - """Render pages into output.""" name = "render_pages" diff --git a/nikola/plugins/task/posts.plugin b/nikola/plugins/task/posts.plugin index 79b7c51..c9578bc 100644 --- a/nikola/plugins/task/posts.plugin +++ b/nikola/plugins/task/posts.plugin @@ -5,7 +5,7 @@ module = posts [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create HTML fragments out of posts. [Nikola] diff --git a/nikola/plugins/task/posts.py b/nikola/plugins/task/posts.py index a3a8375..8735beb 100644 --- a/nikola/plugins/task/posts.py +++ b/nikola/plugins/task/posts.py @@ -44,7 +44,6 @@ def update_deps(post, lang, task): class RenderPosts(Task): - """Build HTML fragments from metadata and text.""" name = "render_posts" @@ -77,6 +76,8 @@ class RenderPosts(Task): deps_dict = copy(kw) deps_dict.pop('timeline') for post in kw['timeline']: + if not post.is_translation_available(lang) and not self.site.config['SHOW_UNTRANSLATED_POSTS']: + continue # Extra config dependencies picked from config for p in post.fragment_deps(lang): if p.startswith('####MAGIC####CONFIG:'): @@ -114,7 +115,7 @@ class RenderPosts(Task): pass else: flist.append(f) - yield utils.apply_filters(task, {os.path.splitext(dest): flist}) + yield utils.apply_filters(task, {os.path.splitext(dest)[-1]: flist}) def dependence_on_timeline(self, post, lang): """Check if a post depends on the timeline.""" diff --git a/nikola/plugins/task/py3_switch.plugin b/nikola/plugins/task/py3_switch.plugin new file mode 100644 index 0000000..b0014e1 --- /dev/null +++ b/nikola/plugins/task/py3_switch.plugin @@ -0,0 +1,13 @@ +[Core] +name = py3_switch +module = py3_switch + +[Documentation] +author = Roberto Alsina +version = 1.0 +website = https://getnikola.com/ +description = Beg the user to switch to Python 3 + +[Nikola] +plugincategory = Task + diff --git a/nikola/plugins/task/py3_switch.py b/nikola/plugins/task/py3_switch.py new file mode 100644 index 0000000..930c593 --- /dev/null +++ b/nikola/plugins/task/py3_switch.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2015 Roberto Alsina 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. + +"""Beg the user to switch to python 3.""" + +import datetime +import os +import random +import sys + +import doit.tools + +from nikola.utils import get_logger, STDERR_HANDLER +from nikola.plugin_categories import LateTask + +PY2_AND_NO_PY3_WARNING = """Nikola is going to deprecate Python 2 support in 2016. Your current +version will continue to work, but please consider upgrading to Python 3. + +Please check http://bit.ly/1FKEsiX for details. +""" +PY2_WARNING = """Nikola is going to deprecate Python 2 support in 2016. You already have Python 3 +available in your system. Why not switch? + +Please check http://bit.ly/1FKEsiX for details. +""" +PY2_BARBS = [ + "Python 2 has been deprecated for years. Stop clinging to your long gone youth and switch to Python3.", + "Python 2 is the safety blanket of languages. Be a big kid and switch to Python 3", + "Python 2 is old and busted. Python 3 is the new hotness.", + "Nice unicode you have there, would be a shame something happened to it.. switch to python 3!.", + "Don’t get in the way of progress! Upgrade to Python 3 and save a developer’s mind today!", + "Winners don't use Python 2 -- Signed: The FBI", + "Python 2? What year is it?", + "I just wanna tell you how I'm feeling\n" + "Gotta make you understand\n" + "Never gonna give you up [But Python 2 has to go]", + "The year 2009 called, and they want their Python 2.7 back.", +] + + +LOGGER = get_logger('Nikola', STDERR_HANDLER) + + +def has_python_3(): + """Check if python 3 is available.""" + if 'win' in sys.platform: + py_bin = 'py.exe' + else: + py_bin = 'python3' + for path in os.environ["PATH"].split(os.pathsep): + if os.access(os.path.join(path, py_bin), os.X_OK): + return True + return False + + +class Py3Switch(LateTask): + """Beg the user to switch to python 3.""" + + name = "_switch to py3" + + def gen_tasks(self): + """Beg the user to switch to python 3.""" + def give_warning(): + if sys.version_info[0] == 3: + return + if has_python_3(): + LOGGER.warn(random.choice(PY2_BARBS)) + LOGGER.warn(PY2_WARNING) + else: + LOGGER.warn(PY2_AND_NO_PY3_WARNING) + + task = { + 'basename': self.name, + 'name': 'please!', + 'actions': [give_warning], + 'clean': True, + 'uptodate': [doit.tools.timeout(datetime.timedelta(days=3))] + } + + return task diff --git a/nikola/plugins/task/redirect.plugin b/nikola/plugins/task/redirect.plugin index c3137b9..c5a3042 100644 --- a/nikola/plugins/task/redirect.plugin +++ b/nikola/plugins/task/redirect.plugin @@ -5,7 +5,7 @@ module = redirect [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create redirect pages. [Nikola] diff --git a/nikola/plugins/task/redirect.py b/nikola/plugins/task/redirect.py index 8530f5e..2d4eba4 100644 --- a/nikola/plugins/task/redirect.py +++ b/nikola/plugins/task/redirect.py @@ -35,7 +35,6 @@ from nikola import utils class Redirect(Task): - """Generate redirections.""" name = "redirect" diff --git a/nikola/plugins/task/robots.plugin b/nikola/plugins/task/robots.plugin index 72ce31f..7ae56c6 100644 --- a/nikola/plugins/task/robots.plugin +++ b/nikola/plugins/task/robots.plugin @@ -5,7 +5,7 @@ module = robots [Documentation] author = Daniel Aleksandersen version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Generate /robots.txt exclusion file and promote sitemap. [Nikola] diff --git a/nikola/plugins/task/robots.py b/nikola/plugins/task/robots.py index 65254b6..7c7f5df 100644 --- a/nikola/plugins/task/robots.py +++ b/nikola/plugins/task/robots.py @@ -39,7 +39,6 @@ from nikola import utils class RobotsFile(LateTask): - """Generate a robots.txt file.""" name = "robots_file" @@ -64,14 +63,15 @@ class RobotsFile(LateTask): with io.open(robots_path, 'w+', encoding='utf8') as outf: outf.write("Sitemap: {0}\n\n".format(sitemapindex_url)) + outf.write("User-Agent: *\n") if kw["robots_exclusions"]: - outf.write("User-Agent: *\n") for loc in kw["robots_exclusions"]: outf.write("Disallow: {0}\n".format(loc)) + outf.write("Host: {0}\n".format(urlparse(kw["base_url"]).netloc)) yield self.group_task() - if not utils.get_asset_path("robots.txt", [], files_folders=kw["files_folders"]): + 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, @@ -82,6 +82,6 @@ class RobotsFile(LateTask): "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 fie.') + 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.') diff --git a/nikola/plugins/task/rss.plugin b/nikola/plugins/task/rss.plugin index cf9b7a7..4dd8aba 100644 --- a/nikola/plugins/task/rss.plugin +++ b/nikola/plugins/task/rss.plugin @@ -5,7 +5,7 @@ module = rss [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Generate RSS feeds. [Nikola] diff --git a/nikola/plugins/task/rss.py b/nikola/plugins/task/rss.py index 9020a06..be57f5c 100644 --- a/nikola/plugins/task/rss.py +++ b/nikola/plugins/task/rss.py @@ -38,7 +38,6 @@ from nikola.plugin_categories import Task class GenerateRSS(Task): - """Generate RSS feeds.""" name = "generate_rss" @@ -58,13 +57,14 @@ class GenerateRSS(Task): "base_url": self.site.config["BASE_URL"], "blog_description": self.site.config["BLOG_DESCRIPTION"], "output_folder": self.site.config["OUTPUT_FOLDER"], - "rss_teasers": self.site.config["RSS_TEASERS"], - "rss_plain": self.site.config["RSS_PLAIN"], + "feed_teasers": self.site.config["FEED_TEASERS"], + "feed_plain": self.site.config["FEED_PLAIN"], "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], "feed_length": self.site.config['FEED_LENGTH'], + "feed_previewimage": self.site.config["FEED_PREVIEWIMAGE"], "tzinfo": self.site.tzinfo, - "rss_read_more_link": self.site.config["RSS_READ_MORE_LINK"], - "rss_links_append_query": self.site.config["RSS_LINKS_APPEND_QUERY"], + "feed_read_more_link": self.site.config["FEED_READ_MORE_LINK"], + "feed_links_append_query": self.site.config["FEED_LINKS_APPEND_QUERY"], } self.site.scan_posts() # Check for any changes in the state of use_in_feeds for any post. @@ -96,8 +96,8 @@ class GenerateRSS(Task): 'actions': [(utils.generic_rss_renderer, (lang, kw["blog_title"](lang), kw["site_url"], kw["blog_description"](lang), posts, output_name, - kw["rss_teasers"], kw["rss_plain"], kw['feed_length'], feed_url, - None, kw["rss_links_append_query"]))], + kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], feed_url, + None, kw["feed_links_append_query"]))], 'task_dep': ['render_posts'], 'clean': True, @@ -106,6 +106,11 @@ class GenerateRSS(Task): yield utils.apply_filters(task, kw['filters']) def rss_path(self, name, lang): - """Return RSS path.""" + """A link to the RSS feed path. + + Example: + + link://rss => /blog/rss.xml + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], self.site.config['RSS_PATH'], 'rss.xml'] if _f] diff --git a/nikola/plugins/task/scale_images.plugin b/nikola/plugins/task/scale_images.plugin index d906b8c..3edd0c6 100644 --- a/nikola/plugins/task/scale_images.plugin +++ b/nikola/plugins/task/scale_images.plugin @@ -5,7 +5,7 @@ module = scale_images [Documentation] author = Pelle Nilsson version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create down-scaled images and thumbnails. [Nikola] diff --git a/nikola/plugins/task/scale_images.py b/nikola/plugins/task/scale_images.py index 22ed2ab..e55dc6c 100644 --- a/nikola/plugins/task/scale_images.py +++ b/nikola/plugins/task/scale_images.py @@ -34,7 +34,6 @@ from nikola import utils class ScaleImage(Task, ImageProcessor): - """Resize images and create thumbnails for them.""" name = "scale_images" diff --git a/nikola/plugins/task/sitemap.plugin b/nikola/plugins/task/sitemap.plugin index e3c991f..83e72c4 100644 --- a/nikola/plugins/task/sitemap.plugin +++ b/nikola/plugins/task/sitemap.plugin @@ -5,7 +5,7 @@ module = sitemap [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Generate google sitemap. [Nikola] diff --git a/nikola/plugins/task/sitemap/__init__.py b/nikola/plugins/task/sitemap/__init__.py index fd781d6..90acdd3 100644 --- a/nikola/plugins/task/sitemap/__init__.py +++ b/nikola/plugins/task/sitemap/__init__.py @@ -31,6 +31,7 @@ import io import datetime import dateutil.tz import os +import sys try: from urlparse import urljoin, urlparse import robotparser as robotparser @@ -39,7 +40,7 @@ except ImportError: import urllib.robotparser as robotparser # NOQA from nikola.plugin_categories import LateTask -from nikola.utils import config_changed, apply_filters +from nikola.utils import apply_filters, config_changed, encodelink urlset_header = """<?xml version="1.0" encoding="UTF-8"?> @@ -106,7 +107,6 @@ def get_base_path(base): class Sitemap(LateTask): - """Generate a sitemap.""" name = "sitemap" @@ -146,7 +146,10 @@ class Sitemap(LateTask): continue # Totally empty, not on sitemap path = os.path.relpath(root, output) # ignore the current directory. - path = (path.replace(os.sep, '/') + '/').replace('./', '') + if path == '.': + path = '' + else: + path = path.replace(os.sep, '/') + '/' lastmod = self.get_lastmod(root) loc = urljoin(base_url, base_path + path) if kw['index_file'] in files and kw['strip_indexes']: # ignore folders when not stripping urls @@ -157,10 +160,10 @@ class Sitemap(LateTask): if post: for lang in kw['translations']: alt_url = post.permalink(lang=lang, absolute=True) - if loc == alt_url: + if encodelink(loc) == alt_url: continue alternates.append(alternates_format.format(lang, alt_url)) - urlset[loc] = loc_format.format(loc, lastmod, ''.join(alternates)) + urlset[loc] = loc_format.format(encodelink(loc), lastmod, ''.join(alternates)) for fname in files: if kw['strip_indexes'] and fname == kw['index_file']: continue # We already mapped the folder @@ -200,7 +203,7 @@ class Sitemap(LateTask): path = path.replace(os.sep, '/') lastmod = self.get_lastmod(real_path) loc = urljoin(base_url, base_path + path) - sitemapindex[loc] = sitemap_format.format(loc, lastmod) + sitemapindex[loc] = sitemap_format.format(encodelink(loc), lastmod) continue else: continue # ignores all XML files except those presumed to be RSS @@ -214,18 +217,22 @@ class Sitemap(LateTask): if post: for lang in kw['translations']: alt_url = post.permalink(lang=lang, absolute=True) - if loc == alt_url: + if encodelink(loc) == alt_url: continue alternates.append(alternates_format.format(lang, alt_url)) - urlset[loc] = loc_format.format(loc, lastmod, '\n'.join(alternates)) + urlset[loc] = loc_format.format(encodelink(loc), lastmod, '\n'.join(alternates)) def robot_fetch(path): """Check if robots can fetch a file.""" for rule in kw["robots_exclusions"]: robot = robotparser.RobotFileParser() robot.parse(["User-Agent: *", "Disallow: {0}".format(rule)]) - if not robot.can_fetch("*", '/' + path): - return False # not robot food + if sys.version_info[0] == 3: + if not robot.can_fetch("*", '/' + path): + return False # not robot food + else: + if not robot.can_fetch("*", ('/' + path).encode('utf-8')): + return False # not robot food return True def write_sitemap(): diff --git a/nikola/plugins/task/sources.plugin b/nikola/plugins/task/sources.plugin index d232c2b..66856f1 100644 --- a/nikola/plugins/task/sources.plugin +++ b/nikola/plugins/task/sources.plugin @@ -5,7 +5,7 @@ module = sources [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Copy page sources into the output. [Nikola] diff --git a/nikola/plugins/task/sources.py b/nikola/plugins/task/sources.py index 87b4ae7..f782ad4 100644 --- a/nikola/plugins/task/sources.py +++ b/nikola/plugins/task/sources.py @@ -33,7 +33,6 @@ from nikola import utils class Sources(Task): - """Copy page sources into the output.""" name = "render_sources" diff --git a/nikola/plugins/task/tags.plugin b/nikola/plugins/task/tags.plugin index 283a16a..c3a5be3 100644 --- a/nikola/plugins/task/tags.plugin +++ b/nikola/plugins/task/tags.plugin @@ -5,7 +5,7 @@ module = tags [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Render the tag pages and feeds. [Nikola] diff --git a/nikola/plugins/task/tags.py b/nikola/plugins/task/tags.py index 3186636..6d9d495 100644 --- a/nikola/plugins/task/tags.py +++ b/nikola/plugins/task/tags.py @@ -41,7 +41,6 @@ from nikola import utils class RenderTags(Task): - """Render the tag/category pages and feeds.""" name = "render_tags" @@ -74,9 +73,9 @@ class RenderTags(Task): 'category_prefix': self.site.config['CATEGORY_PREFIX'], "category_pages_are_indexes": self.site.config['CATEGORY_PAGES_ARE_INDEXES'], "generate_rss": self.site.config['GENERATE_RSS'], - "rss_teasers": self.site.config["RSS_TEASERS"], - "rss_plain": self.site.config["RSS_PLAIN"], - "rss_link_append_query": self.site.config["RSS_LINKS_APPEND_QUERY"], + "feed_teasers": self.site.config["FEED_TEASERS"], + "feed_plain": self.site.config["FEED_PLAIN"], + "feed_link_append_query": self.site.config["FEED_LINKS_APPEND_QUERY"], "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], "feed_length": self.site.config['FEED_LENGTH'], "taglist_minimum_post_count": self.site.config['TAGLIST_MINIMUM_POSTS'], @@ -84,6 +83,10 @@ class RenderTags(Task): "pretty_urls": self.site.config['PRETTY_URLS'], "strip_indexes": self.site.config['STRIP_INDEXES'], "index_file": self.site.config['INDEX_FILE'], + "category_pages_descriptions": self.site.config['CATEGORY_PAGES_DESCRIPTIONS'], + "category_pages_titles": self.site.config['CATEGORY_PAGES_TITLES'], + "tag_pages_descriptions": self.site.config['TAG_PAGES_DESCRIPTIONS'], + "tag_pages_titles": self.site.config['TAG_PAGES_TITLES'], } self.site.scan_posts() @@ -168,7 +171,7 @@ class RenderTags(Task): """Write tag data into JSON file, for use in tag clouds.""" utils.makedirs(os.path.dirname(output_name)) with open(output_name, 'w+') as fd: - json.dump(data, fd) + json.dump(data, fd, sort_keys=True) if self.site.config['WRITE_TAG_CLOUD']: task = { @@ -199,7 +202,6 @@ class RenderTags(Task): kw['tags'] = tags output_name = os.path.join( kw['output_folder'], self.site.path('tag_index' if has_tags else 'category_index', None, lang)) - output_name = output_name context = {} if has_categories and has_tags: context["title"] = kw["messages"][lang]["Tags and Categories"] @@ -251,6 +253,10 @@ class RenderTags(Task): else: return tag + def _get_indexes_title(self, tag, is_category, lang, messages): + titles = self.site.config['CATEGORY_PAGES_TITLES'] if is_category else self.site.config['TAG_PAGES_TITLES'] + return titles[lang][tag] if lang in titles and tag in titles[lang] else messages[lang]["Posts about %s"] % tag + def _get_description(self, tag, is_category, lang): descriptions = self.site.config['CATEGORY_PAGES_DESCRIPTIONS'] if is_category else self.site.config['TAG_PAGES_DESCRIPTIONS'] return descriptions[lang][tag] if lang in descriptions and tag in descriptions[lang] else None @@ -276,7 +282,7 @@ class RenderTags(Task): if kw["generate_rss"]: # On a tag page, the feeds include the tag's feeds rss_link = ("""<link rel="alternate" type="application/rss+xml" """ - """type="application/rss+xml" title="RSS for tag """ + """title="RSS for tag """ """{0} ({1})" href="{2}">""".format( title, lang, self.site.link(kind + "_rss", tag, lang))) context_source['rss_link'] = rss_link @@ -284,7 +290,7 @@ class RenderTags(Task): context_source["category"] = tag context_source["category_path"] = self.site.parse_category_name(tag) context_source["tag"] = title - indexes_title = kw["messages"][lang]["Posts about %s"] % title + indexes_title = self._get_indexes_title(title, is_category, lang, kw["messages"]) context_source["description"] = self._get_description(tag, is_category, lang) if is_category: context_source["subcategories"] = self._get_subcategories(tag) @@ -306,7 +312,7 @@ class RenderTags(Task): context["category"] = tag context["category_path"] = self.site.parse_category_name(tag) context["tag"] = title - context["title"] = kw["messages"][lang]["Posts about %s"] % title + context["title"] = self._get_indexes_title(title, is_category, lang, kw["messages"]) context["posts"] = post_list context["permalink"] = self.site.link(kind, tag, lang) context["kind"] = kind @@ -326,6 +332,29 @@ class RenderTags(Task): task['basename'] = str(self.name) yield task + if self.site.config['GENERATE_ATOM']: + yield self.atom_feed_list(kind, tag, lang, post_list, context, kw) + + def atom_feed_list(self, kind, tag, lang, post_list, context, kw): + """Generate atom feeds for tag lists.""" + if kind == 'tag': + context['feedlink'] = self.site.abs_link(self.site.path('tag_atom', tag, lang)) + feed_path = os.path.join(kw['output_folder'], self.site.path('tag_atom', tag, lang)) + elif kind == 'category': + context['feedlink'] = self.site.abs_link(self.site.path('category_atom', tag, lang)) + feed_path = os.path.join(kw['output_folder'], self.site.path('category_atom', tag, lang)) + + task = { + 'basename': str(self.name), + 'name': feed_path, + 'targets': [feed_path], + 'actions': [(self.site.atom_feed_renderer, (lang, post_list, feed_path, kw['filters'], context))], + 'clean': True, + 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.tags:atom')], + 'task_dep': ['render_posts'], + } + return task + def tag_rss(self, tag, lang, posts, kw, is_category): """Create a RSS feed for a single tag in a given language.""" kind = "category" if is_category else "tag" @@ -349,8 +378,8 @@ class RenderTags(Task): 'actions': [(utils.generic_rss_renderer, (lang, "{0} ({1})".format(kw["blog_title"](lang), self._get_title(tag, is_category)), kw["site_url"], None, post_list, - output_name, kw["rss_teasers"], kw["rss_plain"], kw['feed_length'], - feed_url, None, kw["rss_link_append_query"]))], + output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], + feed_url, None, kw["feed_link_append_query"]))], 'clean': True, 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.tags:rss')] + deps_uptodate, 'task_dep': ['render_posts'], @@ -364,41 +393,71 @@ class RenderTags(Task): return name def tag_index_path(self, name, lang): - """Return path to the tag index.""" - return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'], - self.site.config['INDEX_FILE']] if _f] + """A link to the tag index. + + Example: + + link://tag_index => /tags/index.html + """ + if self.site.config['TAGS_INDEX_PATH'][lang]: + paths = [_f for _f in [self.site.config['TRANSLATIONS'][lang], + self.site.config['TAGS_INDEX_PATH'][lang]] if _f] + else: + paths = [_f for _f in [self.site.config['TRANSLATIONS'][lang], + self.site.config['TAG_PATH'][lang], + self.site.config['INDEX_FILE']] if _f] + return paths def category_index_path(self, name, lang): - """Return path to the category index.""" + """A link to the category index. + + Example: + + link://category_index => /categories/index.html + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['CATEGORY_PATH'], + self.site.config['CATEGORY_PATH'][lang], self.site.config['INDEX_FILE']] if _f] def tag_path(self, name, lang): - """Return path to a tag.""" + """A link to a tag's page. + + Example: + + link://tag/cats => /tags/cats.html + """ if self.site.config['PRETTY_URLS']: return [_f for _f in [ self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'], + self.site.config['TAG_PATH'][lang], self.slugify_tag_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.site.config['TAG_PATH'][lang], self.slugify_tag_name(name) + ".html"] if _f] def tag_atom_path(self, name, lang): - """Return path to a tag Atom feed.""" + """A link to a tag's Atom feed. + + Example: + + link://tag_atom/cats => /tags/cats.atom + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'], self.slugify_tag_name(name) + ".atom"] if + self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name) + ".atom"] if _f] def tag_rss_path(self, name, lang): - """Return path to a tag RSS feed.""" + """A link to a tag's RSS feed. + + Example: + + link://tag_rss/cats => /tags/cats.xml + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'], self.slugify_tag_name(name) + ".xml"] if + self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name) + ".xml"] if _f] def slugify_category_name(self, name): @@ -417,24 +476,39 @@ class RenderTags(Task): return path def category_path(self, name, lang): - """Return path to a category.""" + """A link to a category. + + Example: + + link://category/dogs => /categories/dogs.html + """ if self.site.config['PRETTY_URLS']: return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['CATEGORY_PATH']] if + self.site.config['CATEGORY_PATH'][lang]] if _f] + self.slugify_category_name(name) + [self.site.config['INDEX_FILE']] else: return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['CATEGORY_PATH']] if + self.site.config['CATEGORY_PATH'][lang]] if _f] + self._add_extension(self.slugify_category_name(name), ".html") def category_atom_path(self, name, lang): - """Return path to a category Atom feed.""" + """A link to a category's Atom feed. + + Example: + + link://category_atom/dogs => /categories/dogs.atom + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['CATEGORY_PATH']] if + self.site.config['CATEGORY_PATH'][lang]] if _f] + self._add_extension(self.slugify_category_name(name), ".atom") def category_rss_path(self, name, lang): - """Return path to a category RSS feed.""" + """A link to a category's RSS feed. + + Example: + + link://category_rss/dogs => /categories/dogs.xml + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['CATEGORY_PATH']] if + self.site.config['CATEGORY_PATH'][lang]] if _f] + self._add_extension(self.slugify_category_name(name), ".xml") diff --git a/nikola/plugins/template/jinja.plugin b/nikola/plugins/template/jinja.plugin index cfe9fa8..78fd41b 100644 --- a/nikola/plugins/template/jinja.plugin +++ b/nikola/plugins/template/jinja.plugin @@ -5,7 +5,7 @@ module = jinja [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Support for Jinja2 templates. [Nikola] diff --git a/nikola/plugins/template/jinja.py b/nikola/plugins/template/jinja.py index b02d75c..e7df102 100644 --- a/nikola/plugins/template/jinja.py +++ b/nikola/plugins/template/jinja.py @@ -42,7 +42,6 @@ from nikola.utils import makedirs, req_missing class JinjaTemplates(TemplateSystem): - """Support for Jinja2 templates.""" name = "jinja" diff --git a/nikola/plugins/template/mako.plugin b/nikola/plugins/template/mako.plugin index d256faf..308d291 100644 --- a/nikola/plugins/template/mako.plugin +++ b/nikola/plugins/template/mako.plugin @@ -5,7 +5,7 @@ module = mako [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Support for Mako templates. [Nikola] diff --git a/nikola/plugins/template/mako.py b/nikola/plugins/template/mako.py index aed6596..6da21db 100644 --- a/nikola/plugins/template/mako.py +++ b/nikola/plugins/template/mako.py @@ -44,7 +44,6 @@ LOGGER = get_logger('mako', STDERR_HANDLER) class MakoTemplates(TemplateSystem): - """Support for Mako templates.""" name = "mako" |
