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')
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')
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)
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)
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)
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)