Example #1
0
    def handler(self, chart_type, **_options):
        """Generate chart using Pygal."""
        if pygal is None:
            msg = req_missing(['pygal'],
                              'use the Chart directive',
                              optional=True)
            return '<div class="text-error">{0}</div>'.format(msg)
        options = {}
        chart_data = []
        _options.pop('post', None)
        _options.pop('site')
        data = _options.pop('data')

        for line in data.splitlines():
            line = line.strip()
            if line:
                chart_data.append(literal_eval('({0})'.format(line)))
        if 'data_file' in _options:
            options = load_data(_options['data_file'])
            _options.pop('data_file')
            if not chart_data:  # If there is data in the document, it wins
                for k, v in options.pop('data', {}).items():
                    chart_data.append((k, v))

        options.update(_options)

        style_name = options.pop('style', 'BlueStyle')
        if '(' in style_name:  # Parametric style
            style = eval('pygal.style.' + style_name)
        else:
            style = getattr(pygal.style, style_name)
        for k, v in options.items():
            try:
                options[k] = literal_eval(v)
            except Exception:
                options[k] = v
        chart = pygal
        for o in chart_type.split('.'):
            chart = getattr(chart, o)
        chart = chart(style=style)
        if _site and _site.invariant:
            chart.no_prefix = True
        chart.config(**options)
        for label, series in chart_data:
            chart.add(label, series)
        return chart.render().decode('utf8')
Example #2
0
    def handler(self, chart_type, **_options):
        """Generate chart using Pygal."""
        if pygal is None:
            msg = req_missing(
                ['pygal'], 'use the Chart directive', optional=True)
            return '<div class="text-error">{0}</div>'.format(msg)
        options = {}
        chart_data = []
        _options.pop('post', None)
        _options.pop('site')
        data = _options.pop('data')

        for line in data.splitlines():
            line = line.strip()
            if line:
                chart_data.append(literal_eval('({0})'.format(line)))
        if 'data_file' in _options:
            options = load_data(_options['data_file'])
            _options.pop('data_file')
            if not chart_data:  # If there is data in the document, it wins
                for k, v in options.pop('data', {}).items():
                    chart_data.append((k, v))

        options.update(_options)

        style_name = options.pop('style', 'BlueStyle')
        if '(' in style_name:  # Parametric style
            style = eval('pygal.style.' + style_name)
        else:
            style = getattr(pygal.style, style_name)
        for k, v in options.items():
            try:
                options[k] = literal_eval(v)
            except Exception:
                options[k] = v
        chart = pygal
        for o in chart_type.split('.'):
            chart = getattr(chart, o)
        chart = chart(style=style)
        if _site and _site.invariant:
            chart.no_prefix = True
        chart.config(**options)
        for label, series in chart_data:
            chart.add(label, series)
        return chart.render().decode('utf8')
Example #3
0
    def __init__(self,
                 source_path,
                 config,
                 destination,
                 use_in_feeds,
                 messages,
                 template_name,
                 compiler,
                 destination_base=None,
                 metadata_extractors_by=None):
        """Initialize post.

        The source path is the user created post file. From it we calculate
        the meta file, as well as any translations available, and
        the .html fragment file path.

        destination_base must be None or a TranslatableSetting instance. If
        specified, it will be prepended to the destination path.
        """
        self.config = config
        self.compiler = compiler
        self.compiler_contexts = {}
        self.compile_html = self.compiler.compile
        self.demote_headers = self.compiler.demote_headers and self.config[
            'DEMOTE_HEADERS']
        tzinfo = self.config['__tzinfo__']
        if self.config['FUTURE_IS_NOW']:
            self.current_time = None
        else:
            self.current_time = current_time(tzinfo)
        self.translated_to = set([])
        self._prev_post = None
        self._next_post = None
        self.base_url = self.config['BASE_URL']
        self.is_draft = False
        self.is_private = False
        self.strip_indexes = self.config['STRIP_INDEXES']
        self.index_file = self.config['INDEX_FILE']
        self.pretty_urls = self.config['PRETTY_URLS']
        self.source_path = source_path  # posts/blah.txt
        self.post_name = os.path.splitext(source_path)[0]  # posts/blah
        _relpath = os.path.relpath(self.post_name)
        if _relpath != self.post_name:
            self.post_name = _relpath.replace('..' + os.sep, '_..' + os.sep)
        # cache[\/]posts[\/]blah.html
        self.base_path = os.path.join(self.config['CACHE_FOLDER'],
                                      self.post_name + ".html")
        # cache/posts/blah.html
        self._base_path = self.base_path.replace('\\', '/')
        self.metadata_path = self.post_name + ".meta"  # posts/blah.meta
        self.folder_relative = destination
        self.folder_base = destination_base
        self.default_lang = self.config['DEFAULT_LANG']
        self.translations = self.config['TRANSLATIONS']
        self.messages = messages
        self.skip_untranslated = not self.config['SHOW_UNTRANSLATED_POSTS']
        self._template_name = template_name
        self.is_two_file = True
        self._reading_time = None
        self._remaining_reading_time = None
        self._paragraph_count = None
        self._remaining_paragraph_count = None
        self._dependency_file_fragment = defaultdict(list)
        self._dependency_file_page = defaultdict(list)
        self._dependency_uptodate_fragment = defaultdict(list)
        self._dependency_uptodate_page = defaultdict(list)
        self._depfile = defaultdict(list)
        if metadata_extractors_by is None:
            self.metadata_extractors_by = {'priority': {}, 'source': {}}
        else:
            self.metadata_extractors_by = metadata_extractors_by

        # Load internationalized metadata
        for lang in self.translations:
            if os.path.isfile(
                    get_translation_candidate(self.config, self.source_path,
                                              lang)):
                self.translated_to.add(lang)

        default_metadata, default_used_extractor = get_meta(self, lang=None)

        self.meta = Functionary(lambda: None, self.default_lang)
        self.used_extractor = Functionary(lambda: None, self.default_lang)
        self.meta[self.default_lang] = default_metadata
        self.used_extractor[self.default_lang] = default_used_extractor

        for lang in self.translations:
            if lang != self.default_lang:
                meta = defaultdict(lambda: '')
                meta.update(default_metadata)
                _meta, _extractors = get_meta(self, lang)
                meta.update(_meta)
                self.meta[lang] = meta
                self.used_extractor[lang] = _extractors

        if not self.is_translation_available(self.default_lang):
            # Special case! (Issue #373)
            # Fill default_metadata with stuff from the other languages
            for lang in sorted(self.translated_to):
                default_metadata.update(self.meta[lang])
        # Compose paths
        if self.folder_base is not None:
            # Use translatable destination folders
            self.folders = {}
            for lang in self.config['TRANSLATIONS'].keys():
                if os.path.isabs(self.folder_base(lang)):  # Issue 2982
                    self.folder_base[lang] = os.path.relpath(
                        self.folder_base(lang), '/')
                self.folders[lang] = os.path.normpath(
                    os.path.join(self.folder_base(lang), self.folder_relative))
        else:
            # Old behavior (non-translatable destination path, normalized by scanner)
            self.folders = {
                lang: self.folder_relative
                for lang in self.config['TRANSLATIONS'].keys()
            }
        self.folder = self.folders[self.default_lang]

        # Load data field from metadata
        self.data = Functionary(lambda: None, self.default_lang)
        for lang in self.translations:
            if self.meta[lang].get('data') is not None:
                self.data[lang] = utils.load_data(self.meta[lang]['data'])

        if 'date' not in default_metadata and not use_in_feeds:
            # For pages we don't *really* need a date
            if self.config['__invariant__']:
                default_metadata['date'] = datetime.datetime(2013,
                                                             12,
                                                             31,
                                                             23,
                                                             59,
                                                             59,
                                                             tzinfo=tzinfo)
            else:
                default_metadata['date'] = datetime.datetime.utcfromtimestamp(
                    os.stat(self.source_path).st_ctime).replace(
                        tzinfo=dateutil.tz.tzutc()).astimezone(tzinfo)

        # If time zone is set, build localized datetime.
        try:
            self.date = to_datetime(self.meta[self.default_lang]['date'],
                                    tzinfo)
        except ValueError:
            if not self.meta[self.default_lang]['date']:
                msg = 'Missing date in file {}'.format(source_path)
            else:
                msg = "Invalid date '{0}' in file {1}".format(
                    self.meta[self.default_lang]['date'], source_path)
            LOGGER.error(msg)
            raise ValueError(msg)

        if 'updated' not in default_metadata:
            default_metadata['updated'] = default_metadata.get('date', None)

        self.updated = to_datetime(default_metadata['updated'], tzinfo)

        if 'title' not in default_metadata or 'slug' not in default_metadata \
                or 'date' not in default_metadata:
            raise ValueError(
                "You must set a title (found '{0}'), a slug (found '{1}') and a date (found '{2}')! "
                "[in file {3}]".format(default_metadata.get('title', None),
                                       default_metadata.get('slug', None),
                                       default_metadata.get('date', None),
                                       source_path))

        if 'type' not in default_metadata:
            # default value is 'text'
            default_metadata['type'] = 'text'

        self.publish_later = False if self.current_time is None else self.date >= self.current_time

        is_draft = False
        is_private = False
        self._tags = {}
        for lang in self.translated_to:
            if isinstance(self.meta[lang]['tags'], (list, tuple, set)):
                _tag_list = self.meta[lang]['tags']
            else:
                _tag_list = self.meta[lang]['tags'].split(',')
            self._tags[lang] = natsort.natsorted(
                list(set([x.strip() for x in _tag_list])),
                alg=natsort.ns.F | natsort.ns.IC)
            self._tags[lang] = [t for t in self._tags[lang] if t]
            if 'draft' in [_.lower() for _ in self._tags[lang]]:
                is_draft = True
                LOGGER.debug('The post "{0}" is a draft.'.format(
                    self.source_path))
                self._tags[lang].remove('draft')

            if 'private' in self._tags[lang]:
                is_private = True
                LOGGER.debug('The post "{0}" is private.'.format(
                    self.source_path))
                self._tags[lang].remove('private')

        # While draft comes from the tags, it's not really a tag
        self.is_draft = is_draft
        self.is_private = is_private
        self.is_post = use_in_feeds
        self.use_in_feeds = use_in_feeds and not is_draft and not is_private \
            and not self.publish_later

        # Allow overriding URL_TYPE via meta
        # The check is done here so meta dicts won’t change inside of
        # generic_post_rendere
        self.url_type = self.meta('url_type') or None
        # Register potential extra dependencies
        self.compiler.register_extra_dependencies(self)
Example #4
0
    def __init__(
        self,
        source_path,
        config,
        destination,
        use_in_feeds,
        messages,
        template_name,
        compiler,
        destination_base=None,
        metadata_extractors_by=None
    ):
        """Initialize post.

        The source path is the user created post file. From it we calculate
        the meta file, as well as any translations available, and
        the .html fragment file path.

        destination_base must be None or a TranslatableSetting instance. If
        specified, it will be prepended to the destination path.
        """
        self.config = config
        self.compiler = compiler
        self.compiler_contexts = {}
        self.compile_html = self.compiler.compile
        self.demote_headers = self.compiler.demote_headers and self.config['DEMOTE_HEADERS']
        tzinfo = self.config['__tzinfo__']
        if self.config['FUTURE_IS_NOW']:
            self.current_time = None
        else:
            self.current_time = current_time(tzinfo)
        self.translated_to = set([])
        self._prev_post = None
        self._next_post = None
        self.base_url = self.config['BASE_URL']
        self.is_draft = False
        self.is_private = False
        self.strip_indexes = self.config['STRIP_INDEXES']
        self.index_file = self.config['INDEX_FILE']
        self.pretty_urls = self.config['PRETTY_URLS']
        self.source_path = source_path  # posts/blah.txt
        self.post_name = os.path.splitext(source_path)[0]  # posts/blah
        _relpath = os.path.relpath(self.post_name)
        if _relpath != self.post_name:
            self.post_name = _relpath.replace('..' + os.sep, '_..' + os.sep)
        # cache[\/]posts[\/]blah.html
        self.base_path = os.path.join(self.config['CACHE_FOLDER'], self.post_name + ".html")
        # cache/posts/blah.html
        self._base_path = self.base_path.replace('\\', '/')
        self.metadata_path = self.post_name + ".meta"  # posts/blah.meta
        self.folder_relative = destination
        self.folder_base = destination_base
        self.default_lang = self.config['DEFAULT_LANG']
        self.translations = self.config['TRANSLATIONS']
        self.messages = messages
        self.skip_untranslated = not self.config['SHOW_UNTRANSLATED_POSTS']
        self._template_name = template_name
        self.is_two_file = True
        self._reading_time = None
        self._remaining_reading_time = None
        self._paragraph_count = None
        self._remaining_paragraph_count = None
        self._dependency_file_fragment = defaultdict(list)
        self._dependency_file_page = defaultdict(list)
        self._dependency_uptodate_fragment = defaultdict(list)
        self._dependency_uptodate_page = defaultdict(list)
        self._depfile = defaultdict(list)
        if metadata_extractors_by is None:
            self.metadata_extractors_by = {'priority': {}, 'source': {}}
        else:
            self.metadata_extractors_by = metadata_extractors_by

        # Load internationalized metadata
        for lang in self.translations:
            if os.path.isfile(get_translation_candidate(self.config, self.source_path, lang)):
                self.translated_to.add(lang)

        default_metadata, default_used_extractor = get_meta(self, lang=None)

        self.meta = Functionary(lambda: None, self.default_lang)
        self.used_extractor = Functionary(lambda: None, self.default_lang)
        self.meta[self.default_lang] = default_metadata
        self.used_extractor[self.default_lang] = default_used_extractor

        # Compose paths
        if self.folder_base is not None:
            # Use translatable destination folders
            self.folders = {}
            for lang in self.config['TRANSLATIONS'].keys():
                if os.path.isabs(self.folder_base(lang)):  # Issue 2982
                    self.folder_base[lang] = os.path.relpath(self.folder_base(lang), '/')
                self.folders[lang] = os.path.normpath(os.path.join(self.folder_base(lang), self.folder_relative))
        else:
            # Old behavior (non-translatable destination path, normalized by scanner)
            self.folders = {lang: self.folder_relative for lang in self.config['TRANSLATIONS'].keys()}
        self.folder = self.folders[self.default_lang]

        if 'date' not in default_metadata and not use_in_feeds:
            # For pages we don't *really* need a date
            if self.config['__invariant__']:
                default_metadata['date'] = datetime.datetime(2013, 12, 31, 23, 59, 59, tzinfo=tzinfo)
            else:
                default_metadata['date'] = datetime.datetime.utcfromtimestamp(
                    os.stat(self.source_path).st_ctime).replace(tzinfo=dateutil.tz.tzutc()).astimezone(tzinfo)

        # If time zone is set, build localized datetime.
        try:
            self.date = to_datetime(self.meta[self.default_lang]['date'], tzinfo)
        except ValueError:
            if not self.meta[self.default_lang]['date']:
                msg = 'Missing date in file {}'.format(source_path)
            else:
                msg = "Invalid date '{0}' in file {1}".format(self.meta[self.default_lang]['date'], source_path)
            LOGGER.error(msg)
            raise ValueError(msg)

        if 'updated' not in default_metadata:
            default_metadata['updated'] = default_metadata.get('date', None)

        self.updated = to_datetime(default_metadata['updated'], tzinfo)

        if 'title' not in default_metadata or 'slug' not in default_metadata \
                or 'date' not in default_metadata:
            raise ValueError("You must set a title (found '{0}'), a slug (found '{1}') and a date (found '{2}')! "
                             "[in file {3}]".format(default_metadata.get('title', None),
                                                    default_metadata.get('slug', None),
                                                    default_metadata.get('date', None),
                                                    source_path))

        if 'type' not in default_metadata:
            # default value is 'text'
            default_metadata['type'] = 'text'

        for lang in self.translations:
            if lang != self.default_lang:
                meta = defaultdict(lambda: '')
                meta.update(default_metadata)
                _meta, _extractors = get_meta(self, lang)
                meta.update(_meta)
                self.meta[lang] = meta
                self.used_extractor[lang] = _extractors

        if not self.is_translation_available(self.default_lang):
            # Special case! (Issue #373)
            # Fill default_metadata with stuff from the other languages
            for lang in sorted(self.translated_to):
                default_metadata.update(self.meta[lang])

        # Load data field from metadata
        self.data = Functionary(lambda: None, self.default_lang)
        for lang in self.translations:
            if self.meta[lang].get('data') is not None:
                self.data[lang] = utils.load_data(self.meta[lang]['data'])

        for lang, meta in self.meta.items():
            # Migrate section to category
            # TODO: remove in v9
            if 'section' in meta:
                if 'category' in meta:
                    LOGGER.warn("Post {0} has both 'category' and 'section' metadata. Section will be ignored.".format(source_path))
                else:
                    meta['category'] = meta['section']
                    LOGGER.notice("Post {0} uses 'section' metadata, setting its value to 'category'".format(source_path))

            # Handle CATEGORY_DESTPATH_AS_DEFAULT
            if 'category' not in meta and self.config['CATEGORY_DESTPATH_AS_DEFAULT']:
                self.category_from_destpath = True
                if self.config['CATEGORY_DESTPATH_TRIM_PREFIX'] and self.folder_relative != '.':
                    category = self.folder_relative
                else:
                    category = self.folders[lang]
                category = category.replace(os.sep, '/')
                if self.config['CATEGORY_DESTPATH_FIRST_DIRECTORY_ONLY']:
                    category = category.split('/')[0]
                meta['category'] = self.config['CATEGORY_DESTPATH_NAMES'](lang).get(category, category)
            else:
                self.category_from_destpath = False

        self.publish_later = False if self.current_time is None else self.date >= self.current_time

        self.is_draft = False
        self.is_private = False
        self.post_status = 'published'
        self._tags = {}
        self.has_oldstyle_metadata_tags = False
        for lang in self.translated_to:
            if isinstance(self.meta[lang]['tags'], (list, tuple, set)):
                _tag_list = self.meta[lang]['tags']
            else:
                _tag_list = self.meta[lang]['tags'].split(',')
            self._tags[lang] = natsort.natsorted(
                list(set([x.strip() for x in _tag_list])),
                alg=natsort.ns.F | natsort.ns.IC)
            self._tags[lang] = [t for t in self._tags[lang] if t]

            status = self.meta[lang].get('status')
            if status:
                if status == 'published':
                    pass  # already set before, mixing published + something else should result in the other thing
                elif status == 'featured':
                    self.post_status = status
                elif status == 'private':
                    self.post_status = status
                    self.is_private = True
                elif status == 'draft':
                    self.post_status = status
                    self.is_draft = True
                else:
                    LOGGER.warn(('The post "{0}" has the unknown status "{1}". '
                                 'Valid values are "published", "featured", "private" and "draft".').format(self.source_path, status))

            if self.config['WARN_ABOUT_TAG_METADATA']:
                show_warning = False
                if 'draft' in [_.lower() for _ in self._tags[lang]]:
                    LOGGER.warn('The post "{0}" uses the "draft" tag.'.format(self.source_path))
                    show_warning = True
                if 'private' in self._tags[lang]:
                    LOGGER.warn('The post "{0}" uses the "private" tag.'.format(self.source_path))
                    show_warning = True
                if 'mathjax' in self._tags[lang]:
                    LOGGER.warn('The post "{0}" uses the "mathjax" tag.'.format(self.source_path))
                    show_warning = True
                if show_warning:
                    LOGGER.warn('It is suggested that you convert special tags to metadata and set '
                                'USE_TAG_METADATA to False. You can use the upgrade_metadata_v8 '
                                'command plugin for conversion (install with: nikola plugin -i '
                                'upgrade_metadata_v8). Change the WARN_ABOUT_TAG_METADATA '
                                'configuration to disable this warning.')
            if self.config['USE_TAG_METADATA']:
                if 'draft' in [_.lower() for _ in self._tags[lang]]:
                    self.is_draft = True
                    LOGGER.debug('The post "{0}" is a draft.'.format(self.source_path))
                    self._tags[lang].remove('draft')
                    self.post_status = 'draft'
                    self.has_oldstyle_metadata_tags = True

                if 'private' in self._tags[lang]:
                    self.is_private = True
                    LOGGER.debug('The post "{0}" is private.'.format(self.source_path))
                    self._tags[lang].remove('private')
                    self.post_status = 'private'
                    self.has_oldstyle_metadata_tags = True

                if 'mathjax' in self._tags[lang]:
                    self.has_oldstyle_metadata_tags = True

        # While draft comes from the tags, it's not really a tag
        self.is_post = use_in_feeds
        self.use_in_feeds = self.is_post and not self.is_draft and not self.is_private and not self.publish_later

        # Allow overriding URL_TYPE via meta
        # The check is done here so meta dicts won’t change inside of
        # generic_post_rendere
        self.url_type = self.meta('url_type') or None
        # Register potential extra dependencies
        self.compiler.register_extra_dependencies(self)
Example #5
0
    def __init__(
        self,
        source_path,
        config,
        destination,
        use_in_feeds,
        messages,
        template_name,
        compiler,
        destination_base=None
    ):
        """Initialize post.

        The source path is the user created post file. From it we calculate
        the meta file, as well as any translations available, and
        the .html fragment file path.

        destination_base must be None or a TranslatableSetting instance. If
        specified, it will be prepended to the destination path.
        """
        self.config = config
        self.compiler = compiler
        self.compile_html = self.compiler.compile
        self.demote_headers = self.compiler.demote_headers and self.config['DEMOTE_HEADERS']
        tzinfo = self.config['__tzinfo__']
        if self.config['FUTURE_IS_NOW']:
            self.current_time = None
        else:
            self.current_time = current_time(tzinfo)
        self.translated_to = set([])
        self._prev_post = None
        self._next_post = None
        self.base_url = self.config['BASE_URL']
        self.is_draft = False
        self.is_private = False
        self.strip_indexes = self.config['STRIP_INDEXES']
        self.index_file = self.config['INDEX_FILE']
        self.pretty_urls = self.config['PRETTY_URLS']
        self.source_path = source_path  # posts/blah.txt
        self.post_name = os.path.splitext(source_path)[0]  # posts/blah
        # cache[\/]posts[\/]blah.html
        self.base_path = os.path.join(self.config['CACHE_FOLDER'], self.post_name + ".html")
        # cache/posts/blah.html
        self._base_path = self.base_path.replace('\\', '/')
        self.metadata_path = self.post_name + ".meta"  # posts/blah.meta
        self.folder_relative = destination
        self.folder_base = destination_base
        self.default_lang = self.config['DEFAULT_LANG']
        self.translations = self.config['TRANSLATIONS']
        self.messages = messages
        self.skip_untranslated = not self.config['SHOW_UNTRANSLATED_POSTS']
        self._template_name = template_name
        self.is_two_file = True
        self.newstylemeta = True
        self._reading_time = None
        self._remaining_reading_time = None
        self._paragraph_count = None
        self._remaining_paragraph_count = None
        self._dependency_file_fragment = defaultdict(list)
        self._dependency_file_page = defaultdict(list)
        self._dependency_uptodate_fragment = defaultdict(list)
        self._dependency_uptodate_page = defaultdict(list)
        self._depfile = defaultdict(list)

        default_metadata, self.newstylemeta = get_meta(self, self.config['FILE_METADATA_REGEXP'], self.config['UNSLUGIFY_TITLES'])

        self.meta = Functionary(lambda: None, self.default_lang)
        self.meta[self.default_lang] = default_metadata

        # Load internationalized metadata
        for lang in self.translations:
            if os.path.isfile(get_translation_candidate(self.config, self.source_path, lang)):
                self.translated_to.add(lang)
            if lang != self.default_lang:
                meta = defaultdict(lambda: '')
                meta.update(default_metadata)
                _meta, _nsm = get_meta(self, self.config['FILE_METADATA_REGEXP'], self.config['UNSLUGIFY_TITLES'], lang)
                self.newstylemeta = self.newstylemeta and _nsm
                meta.update(_meta)
                self.meta[lang] = meta

        if not self.is_translation_available(self.default_lang):
            # Special case! (Issue #373)
            # Fill default_metadata with stuff from the other languages
            for lang in sorted(self.translated_to):
                default_metadata.update(self.meta[lang])

        # Compose paths
        if self.folder_base is not None:
            # Use translatable destination folders
            self.folders = {}
            for lang in self.config['TRANSLATIONS'].keys():
                self.folders[lang] = os.path.normpath(os.path.join(
                    self.folder_base(lang), self.folder_relative))
        else:
            # Old behavior (non-translatable destination path, normalized by scanner)
            self.folders = {lang: self.folder_relative for lang in self.config['TRANSLATIONS'].keys()}
        self.folder = self.folders[self.default_lang]

        # Load data field from metadata
        self.data = Functionary(lambda: None, self.default_lang)
        for lang in self.translations:
            if self.meta[lang].get('data') is not None:
                self.data[lang] = utils.load_data(self.meta[lang]['data'])

        if 'date' not in default_metadata and not use_in_feeds:
            # For pages we don't *really* need a date
            if self.config['__invariant__']:
                default_metadata['date'] = datetime.datetime(2013, 12, 31, 23, 59, 59, tzinfo=tzinfo)
            else:
                default_metadata['date'] = datetime.datetime.utcfromtimestamp(
                    os.stat(self.source_path).st_ctime).replace(tzinfo=dateutil.tz.tzutc()).astimezone(tzinfo)

        # If time zone is set, build localized datetime.
        try:
            self.date = to_datetime(self.meta[self.default_lang]['date'], tzinfo)
        except ValueError:
            raise ValueError("Invalid date '{0}' in file {1}".format(self.meta[self.default_lang]['date'], source_path))

        if 'updated' not in default_metadata:
            default_metadata['updated'] = default_metadata.get('date', None)

        self.updated = to_datetime(default_metadata['updated'])

        if 'title' not in default_metadata or 'slug' not in default_metadata \
                or 'date' not in default_metadata:
            raise ValueError("You must set a title (found '{0}'), a slug (found '{1}') and a date (found '{2}')! "
                             "[in file {3}]".format(default_metadata.get('title', None),
                                                    default_metadata.get('slug', None),
                                                    default_metadata.get('date', None),
                                                    source_path))

        if 'type' not in default_metadata:
            # default value is 'text'
            default_metadata['type'] = 'text'

        self.publish_later = False if self.current_time is None else self.date >= self.current_time

        is_draft = False
        is_private = False
        self._tags = {}
        for lang in self.translated_to:
            self._tags[lang] = natsort.natsorted(
                list(set([x.strip() for x in self.meta[lang]['tags'].split(',')])),
                alg=natsort.ns.F | natsort.ns.IC)
            self._tags[lang] = [t for t in self._tags[lang] if t]
            if 'draft' in [_.lower() for _ in self._tags[lang]]:
                is_draft = True
                LOGGER.debug('The post "{0}" is a draft.'.format(self.source_path))
                self._tags[lang].remove('draft')

            # TODO: remove in v8
            if 'retired' in self._tags[lang]:
                is_private = True
                LOGGER.warning('The "retired" tag in post "{0}" is now deprecated and will be removed in v8.  Use "private" instead.'.format(self.source_path))
                self._tags[lang].remove('retired')
            # end remove in v8

            if 'private' in self._tags[lang]:
                is_private = True
                LOGGER.debug('The post "{0}" is private.'.format(self.source_path))
                self._tags[lang].remove('private')

        # While draft comes from the tags, it's not really a tag
        self.is_draft = is_draft
        self.is_private = is_private
        self.is_post = use_in_feeds
        self.use_in_feeds = use_in_feeds and not is_draft and not is_private \
            and not self.publish_later

        # Allow overriding URL_TYPE via meta
        # The check is done here so meta dicts won’t change inside of
        # generic_post_rendere
        self.url_type = self.meta('url_type') or None
        # Register potential extra dependencies
        self.compiler.register_extra_dependencies(self)
Example #6
0
    def __init__(self,
                 source_path,
                 config,
                 destination,
                 use_in_feeds,
                 messages,
                 template_name,
                 compiler,
                 destination_base=None,
                 metadata_extractors_by=None):
        """Initialize post.

        The source path is the user created post file. From it we calculate
        the meta file, as well as any translations available, and
        the .html fragment file path.

        destination_base must be None or a TranslatableSetting instance. If
        specified, it will be prepended to the destination path.
        """
        self.config = config
        self.compiler = compiler
        self.compiler_contexts = {}
        self.compile_html = self.compiler.compile
        self.demote_headers = self.compiler.demote_headers and self.config[
            'DEMOTE_HEADERS']
        tzinfo = self.config['__tzinfo__']
        if self.config['FUTURE_IS_NOW']:
            self.current_time = None
        else:
            self.current_time = current_time(tzinfo)
        self.translated_to = set([])
        self._prev_post = None
        self._next_post = None
        self.base_url = self.config['BASE_URL']
        self.is_draft = False
        self.is_private = False
        self.strip_indexes = self.config['STRIP_INDEXES']
        self.index_file = self.config['INDEX_FILE']
        self.pretty_urls = self.config['PRETTY_URLS']
        self.source_path = source_path  # posts/blah.txt
        self.post_name = os.path.splitext(source_path)[0]  # posts/blah
        _relpath = os.path.relpath(self.post_name)
        if _relpath != self.post_name:
            self.post_name = _relpath.replace('..' + os.sep, '_..' + os.sep)
        # cache[\/]posts[\/]blah.html
        self.base_path = os.path.join(self.config['CACHE_FOLDER'],
                                      self.post_name + ".html")
        # cache/posts/blah.html
        self._base_path = self.base_path.replace('\\', '/')
        self.metadata_path = self.post_name + ".meta"  # posts/blah.meta
        self.folder_relative = destination
        self.folder_base = destination_base
        self.default_lang = self.config['DEFAULT_LANG']
        self.translations = self.config['TRANSLATIONS']
        self.messages = messages
        self.skip_untranslated = not self.config['SHOW_UNTRANSLATED_POSTS']
        self._template_name = template_name
        self.is_two_file = True
        self._reading_time = None
        self._remaining_reading_time = None
        self._paragraph_count = None
        self._remaining_paragraph_count = None
        self._dependency_file_fragment = defaultdict(list)
        self._dependency_file_page = defaultdict(list)
        self._dependency_uptodate_fragment = defaultdict(list)
        self._dependency_uptodate_page = defaultdict(list)
        self._depfile = defaultdict(list)
        if metadata_extractors_by is None:
            self.metadata_extractors_by = {'priority': {}, 'source': {}}
        else:
            self.metadata_extractors_by = metadata_extractors_by

        # Load internationalized metadata
        for lang in self.translations:
            if os.path.isfile(
                    get_translation_candidate(self.config, self.source_path,
                                              lang)):
                self.translated_to.add(lang)

        default_metadata, default_used_extractor = get_meta(self, lang=None)

        self.meta = Functionary(lambda: None, self.default_lang)
        self.used_extractor = Functionary(lambda: None, self.default_lang)
        self.meta[self.default_lang] = default_metadata
        self.used_extractor[self.default_lang] = default_used_extractor

        # Compose paths
        if self.folder_base is not None:
            # Use translatable destination folders
            self.folders = {}
            for lang in self.config['TRANSLATIONS'].keys():
                if os.path.isabs(self.folder_base(lang)):  # Issue 2982
                    self.folder_base[lang] = os.path.relpath(
                        self.folder_base(lang), '/')
                self.folders[lang] = os.path.normpath(
                    os.path.join(self.folder_base(lang), self.folder_relative))
        else:
            # Old behavior (non-translatable destination path, normalized by scanner)
            self.folders = {
                lang: self.folder_relative
                for lang in self.config['TRANSLATIONS'].keys()
            }
        self.folder = self.folders[self.default_lang]

        if 'date' not in default_metadata and not use_in_feeds:
            # For pages we don't *really* need a date
            if self.config['__invariant__']:
                default_metadata['date'] = datetime.datetime(2013,
                                                             12,
                                                             31,
                                                             23,
                                                             59,
                                                             59,
                                                             tzinfo=tzinfo)
            else:
                default_metadata['date'] = datetime.datetime.utcfromtimestamp(
                    os.stat(self.source_path).st_ctime).replace(
                        tzinfo=dateutil.tz.tzutc()).astimezone(tzinfo)

        # If time zone is set, build localized datetime.
        try:
            self.date = to_datetime(self.meta[self.default_lang]['date'],
                                    tzinfo)
        except ValueError:
            if not self.meta[self.default_lang]['date']:
                msg = 'Missing date in file {}'.format(source_path)
            else:
                msg = "Invalid date '{0}' in file {1}".format(
                    self.meta[self.default_lang]['date'], source_path)
            LOGGER.error(msg)
            raise ValueError(msg)

        if 'updated' not in default_metadata:
            default_metadata['updated'] = default_metadata.get('date', None)

        self.updated = to_datetime(default_metadata['updated'], tzinfo)

        if 'title' not in default_metadata or 'slug' not in default_metadata \
                or 'date' not in default_metadata:
            raise ValueError(
                "You must set a title (found '{0}'), a slug (found '{1}') and a date (found '{2}')! "
                "[in file {3}]".format(default_metadata.get('title', None),
                                       default_metadata.get('slug', None),
                                       default_metadata.get('date', None),
                                       source_path))

        if 'type' not in default_metadata:
            # default value is 'text'
            default_metadata['type'] = 'text'

        for lang in self.translations:
            if lang != self.default_lang:
                meta = defaultdict(lambda: '')
                meta.update(default_metadata)
                _meta, _extractors = get_meta(self, lang)
                meta.update(_meta)
                self.meta[lang] = meta
                self.used_extractor[lang] = _extractors

        if not self.is_translation_available(self.default_lang):
            # Special case! (Issue #373)
            # Fill default_metadata with stuff from the other languages
            for lang in sorted(self.translated_to):
                default_metadata.update(self.meta[lang])

        # Load data field from metadata
        self.data = Functionary(lambda: None, self.default_lang)
        for lang in self.translations:
            if self.meta[lang].get('data') is not None:
                self.data[lang] = utils.load_data(self.meta[lang]['data'])

        for lang, meta in self.meta.items():
            # Migrate section to category
            # TODO: remove in v9
            if 'section' in meta:
                if 'category' in meta:
                    LOGGER.warning(
                        "Post {0} has both 'category' and 'section' metadata. Section will be ignored."
                        .format(source_path))
                else:
                    meta['category'] = meta['section']
                    LOGGER.info(
                        "Post {0} uses 'section' metadata, setting its value to 'category'"
                        .format(source_path))

            # Handle CATEGORY_DESTPATH_AS_DEFAULT
            if 'category' not in meta and self.config[
                    'CATEGORY_DESTPATH_AS_DEFAULT']:
                self.category_from_destpath = True
                if self.config[
                        'CATEGORY_DESTPATH_TRIM_PREFIX'] and self.folder_relative != '.':
                    category = self.folder_relative
                else:
                    category = self.folders[lang]
                category = category.replace(os.sep, '/')
                if self.config['CATEGORY_DESTPATH_FIRST_DIRECTORY_ONLY']:
                    category = category.split('/')[0]
                meta['category'] = self.config['CATEGORY_DESTPATH_NAMES'](
                    lang).get(category, category)
            else:
                self.category_from_destpath = False

        self.publish_later = False if self.current_time is None else self.date >= self.current_time

        self.is_draft = False
        self.is_private = False
        self.post_status = 'published'
        self._tags = {}
        self.has_oldstyle_metadata_tags = False
        for lang in self.translated_to:
            if isinstance(self.meta[lang]['tags'], (list, tuple, set)):
                _tag_list = self.meta[lang]['tags']
            else:
                _tag_list = self.meta[lang]['tags'].split(',')
            self._tags[lang] = natsort.natsorted(
                list(set([x.strip() for x in _tag_list])),
                alg=natsort.ns.F | natsort.ns.IC)
            self._tags[lang] = [t for t in self._tags[lang] if t]

            status = self.meta[lang].get('status')
            if status:
                if status == 'published':
                    pass  # already set before, mixing published + something else should result in the other thing
                elif status == 'featured':
                    self.post_status = status
                elif status == 'private':
                    self.post_status = status
                    self.is_private = True
                elif status == 'draft':
                    self.post_status = status
                    self.is_draft = True
                else:
                    LOGGER.warning((
                        'The post "{0}" has the unknown status "{1}". '
                        'Valid values are "published", "featured", "private" and "draft".'
                    ).format(self.source_path, status))

            if self.config['WARN_ABOUT_TAG_METADATA']:
                show_warning = False
                if 'draft' in [_.lower() for _ in self._tags[lang]]:
                    LOGGER.warning(
                        'The post "{0}" uses the "draft" tag.'.format(
                            self.source_path))
                    show_warning = True
                if 'private' in self._tags[lang]:
                    LOGGER.warning(
                        'The post "{0}" uses the "private" tag.'.format(
                            self.source_path))
                    show_warning = True
                if 'mathjax' in self._tags[lang]:
                    LOGGER.warning(
                        'The post "{0}" uses the "mathjax" tag.'.format(
                            self.source_path))
                    show_warning = True
                if show_warning:
                    LOGGER.warning(
                        'It is suggested that you convert special tags to metadata and set '
                        'USE_TAG_METADATA to False. You can use the upgrade_metadata_v8 '
                        'command plugin for conversion (install with: nikola plugin -i '
                        'upgrade_metadata_v8). Change the WARN_ABOUT_TAG_METADATA '
                        'configuration to disable this warning.')
            if self.config['USE_TAG_METADATA']:
                if 'draft' in [_.lower() for _ in self._tags[lang]]:
                    self.is_draft = True
                    LOGGER.debug('The post "{0}" is a draft.'.format(
                        self.source_path))
                    self._tags[lang].remove('draft')
                    self.post_status = 'draft'
                    self.has_oldstyle_metadata_tags = True

                if 'private' in self._tags[lang]:
                    self.is_private = True
                    LOGGER.debug('The post "{0}" is private.'.format(
                        self.source_path))
                    self._tags[lang].remove('private')
                    self.post_status = 'private'
                    self.has_oldstyle_metadata_tags = True

                if 'mathjax' in self._tags[lang]:
                    self.has_oldstyle_metadata_tags = True

        # While draft comes from the tags, it's not really a tag
        self.is_post = use_in_feeds
        self.use_in_feeds = self.is_post and not self.is_draft and not self.is_private and not self.publish_later

        # Allow overriding URL_TYPE via meta
        # The check is done here so meta dicts won’t change inside of
        # generic_post_rendere
        self.url_type = self.meta('url_type') or None
        # Register potential extra dependencies
        self.compiler.register_extra_dependencies(self)