From 812f1da659e514d12e3a5e2f4bb665b27f6328f2 Mon Sep 17 00:00:00 2001 From: "Schoenberger, Philipp" Date: Sat, 13 Jul 2019 18:43:35 +0200 Subject: [PATCH] add sitemap plugin remove projects and use cathegories instead --- .../02_3d_printer_en.md} | 6 +- content/blog/03_quadrocopter_en.md | 9 + content/extra/robots.txt | 6 + content/pages/index_de.md | 15 +- content/pages/index_en.md | 2 + content/pages/projects/projects.md | 14 - pelicanconf.py | 38 ++- plugins/sitemap/Readme.rst | 78 +++++ plugins/sitemap/__init__.py | 1 + plugins/sitemap/__init__.pyc | Bin 0 -> 190 bytes plugins/sitemap/sitemap.py | 273 ++++++++++++++++++ plugins/sitemap/sitemap.pyc | Bin 0 -> 8341 bytes themes/minimal/templates/projects.html | 12 +- 13 files changed, 417 insertions(+), 37 deletions(-) rename content/{pages/projects/01_3d_printer.md => blog/02_3d_printer_en.md} (54%) create mode 100644 content/blog/03_quadrocopter_en.md create mode 100644 content/extra/robots.txt delete mode 100644 content/pages/projects/projects.md create mode 100644 plugins/sitemap/Readme.rst create mode 100644 plugins/sitemap/__init__.py create mode 100644 plugins/sitemap/__init__.pyc create mode 100644 plugins/sitemap/sitemap.py create mode 100644 plugins/sitemap/sitemap.pyc diff --git a/content/pages/projects/01_3d_printer.md b/content/blog/02_3d_printer_en.md similarity index 54% rename from content/pages/projects/01_3d_printer.md rename to content/blog/02_3d_printer_en.md index ade799f..e9856cd 100644 --- a/content/pages/projects/01_3d_printer.md +++ b/content/blog/02_3d_printer_en.md @@ -1,10 +1,8 @@ -title: Projects +title: 3d printer date: 2019-05-20 author: Philipp Schönberger -template: page +tags: 3d printer, ender2, cat category: projects -url: projects/01_3d_printer.html -save_as: projects/01_3d_printer.html Here you can view the.. latest projects i've started. diff --git a/content/blog/03_quadrocopter_en.md b/content/blog/03_quadrocopter_en.md new file mode 100644 index 0000000..e40db7f --- /dev/null +++ b/content/blog/03_quadrocopter_en.md @@ -0,0 +1,9 @@ +title: quadrocopter +date: 2019-05-20 +author: Philipp Schönberger +tags: bataflight, quadrocopter, fpv +category: projects + + +Here you can view the.. latest projects i've started. + diff --git a/content/extra/robots.txt b/content/extra/robots.txt new file mode 100644 index 0000000..c5e3055 --- /dev/null +++ b/content/extra/robots.txt @@ -0,0 +1,6 @@ +User-agent: * +Disallow: /stats/ +Allow: / + +Sitemap: http://www.phschoen.de/sitemap.xml + diff --git a/content/pages/index_de.md b/content/pages/index_de.md index 6ff5338..dad2c77 100644 --- a/content/pages/index_de.md +++ b/content/pages/index_de.md @@ -2,17 +2,18 @@ title: home author: Philipp Schönberger lang: de template: page +save_as: index.html -Welcome to my webpage. +Willkommen auf meiner Website. -It contains things I work or worked on and lot of other things I had fun with. +Hier kannst du Arbeiten und Projekte finden an denen ich gearbeitet habe oder Dinge an denen ich Freude hatte. -I’m Philipp Schönberger born in in Zeitz(Germany) within the 1988. -In Constance I made my bachelor degree within ‘computer engineering‘. -Afterwards I absolved my master degree within the ‘robotics and intelligent embedded systems‘ in Lübeck. -Currently I am employed as software engineer/architect for a satellite aided router in Germany near the Lake Constance. +Ich selbst bin Philipp Schönberger in Zeitz im Jahre 1988 geboren . +In Konstanz habe ich meinen Bachelor Abschluss gemacht in "Technischer Informatik" +Anschließend habe ich noch meinen Master in "Robotik und Intelligente eingebettete Systeme' in Lübeck gemacht. + +Im Moment bin ich als Software-Ingenieur und -Architekt für einen Satelliten gestützten Router am Standort Bodensee angestellt. -Enjoy your stay. ## Contact ## diff --git a/content/pages/index_en.md b/content/pages/index_en.md index 2d6a020..3313d09 100644 --- a/content/pages/index_en.md +++ b/content/pages/index_en.md @@ -2,6 +2,8 @@ title: home author: Philipp Schönberger lang: en template: page +save_as: index.html + Welcome to my webpage. diff --git a/content/pages/projects/projects.md b/content/pages/projects/projects.md deleted file mode 100644 index a3e7ffe..0000000 --- a/content/pages/projects/projects.md +++ /dev/null @@ -1,14 +0,0 @@ -title: Projects -date: 2019-05-20 -author: Philipp Schönberger -template: projects -category: projects -url: projects/index.html -save_as: projects/index.html - - -Here you can view the latest projects i've started. - -This page contains tutorials, rough drafts I work on. The articles in here aren't necessarily finished and some of them aren't open to discussion, because they are work in progress (or chaos). Some of them might be turned into self-contained blog posts, some of them will never get out of this dungeon. - - diff --git a/pelicanconf.py b/pelicanconf.py index 2e779f7..0e63ae5 100644 --- a/pelicanconf.py +++ b/pelicanconf.py @@ -10,6 +10,15 @@ EMAIL = 'mail AT phschoen.de' SITENAME = 'https://phschoen.de' OUTPUT_PATH = 'output/' +STATIC_PATHS = [ + 'images', + 'extra', # this +] +EXTRA_PATH_METADATA = { + 'extra/robots.txt': {'path': 'robots.txt'}, + 'extra/favicon.ico': {'path': 'favicon.ico'}, +} + # Base URL this page is hosted at: SITENAME = 'phschoen.de' # SITEURL = 'http://skylx125.ndsatcom.com:8000' @@ -104,7 +113,6 @@ INDEX_SAVE_AS = 'blog/index.html' # Navigation menu: SECTIONS = [('blog', '/blog'), - ('Projects', '/projects'), ('Gallery', '/gallery'), ('about', '/about'), ('Impressum', '/impressum'), @@ -127,13 +135,25 @@ DEFAULT_CATEGORY = 'uncategorized' # DEFAULT_PAGINATION = 10 PLUGIN_PATHS = ['plugins', ] -PLUGINS = ['lightgallery', 'i18n_subsites'] - +PLUGINS = ['lightgallery', 'i18n_subsites', 'sitemap'] + +SITEMAP = { + 'format': 'xml', + 'priorities': { + 'articles': 0.5, + 'indexes': 0.5, + 'pages': 0.5 + }, + 'changefreqs': { + 'articles': 'weekly', + 'indexes': 'daily', + 'pages': 'weekly' + } +} GITHUB_SOURCE_PATH = "wooot" -I18N_SUBSITES = { - 'de': {'SITENAME': 'phschoen.de', +I18N_SUBSITES = {'de': {'SITENAME': 'phschoen.de', 'THEME_STATIC_DIR': 'theme', 'OUTPUT_PATH': './output/de', }, @@ -145,12 +165,14 @@ I18N_SUBSITES = { } languages_lookup = {'en': {'name': 'English', - 'icon': SITEURL+'/images/en.jpg' + 'icon': SITEURL + '/images/en.jpg' }, 'de': {'name': 'Deutsch', - 'icon': SITEURL+'/images/de.jpg' + 'icon': SITEURL + '/images/de.jpg' }, } + + def lookup_lang_ico(lang_code): return languages_lookup[lang_code]['icon'] @@ -165,10 +187,12 @@ def getGitHubPage(source_file): def getBasename(path): + import ntpath return ntpath.basename(path) def month_name(month_number): + import calendar return calendar.month_name[month_number] diff --git a/plugins/sitemap/Readme.rst b/plugins/sitemap/Readme.rst new file mode 100644 index 0000000..7c09189 --- /dev/null +++ b/plugins/sitemap/Readme.rst @@ -0,0 +1,78 @@ +Sitemap +------- + +This plugin generates plain-text or XML sitemaps. You can use the ``SITEMAP`` +variable in your settings file to configure the behavior of the plugin. + +The ``SITEMAP`` variable must be a Python dictionary and can contain these keys: + +- ``format``, which sets the output format of the plugin (``xml`` or ``txt``) + +- ``priorities``, which is a dictionary with three keys: + + - ``articles``, the priority for the URLs of the articles and their + translations + + - ``pages``, the priority for the URLs of the static pages + + - ``indexes``, the priority for the URLs of the index pages, such as tags, + author pages, categories indexes, archives, etc... + + All the values of this dictionary must be decimal numbers between ``0`` and ``1``. + +- ``changefreqs``, which is a dictionary with three items: + + - ``articles``, the update frequency of the articles + + - ``pages``, the update frequency of the pages + + - ``indexes``, the update frequency of the index pages + + Valid frequency values are ``always``, ``hourly``, ``daily``, ``weekly``, ``monthly``, + ``yearly`` and ``never``. + +You can exclude URLs from being included in the sitemap via regular expressions. +For example, to exclude all URLs containing ``tag/`` or ``category/`` you can +use the following ``SITEMAP`` setting. + +.. code-block:: python + + SITEMAP = { + 'exclude': ['tag/', 'category/'] + } + +If a key is missing or a value is incorrect, it will be replaced with the +default value. + +You can also exclude an individual URL by adding metadata to it setting ``private`` +to ``True``. + +The sitemap is saved in ``/sitemap.``. + +.. note:: + ``priorities`` and ``changefreqs`` are information for search engines. + They are only used in the XML sitemaps. + For more information: + +**Example** + +Here is an example configuration (it's also the default settings): + +.. code-block:: python + # Where your plug-ins reside + PLUGIN_PATHS = ['/where/you/cloned/it/pelican-plugins/', ] + PLUGINS=['sitemap',] + + SITEMAP = { + 'format': 'xml', + 'priorities': { + 'articles': 0.5, + 'indexes': 0.5, + 'pages': 0.5 + }, + 'changefreqs': { + 'articles': 'monthly', + 'indexes': 'daily', + 'pages': 'monthly' + } + } diff --git a/plugins/sitemap/__init__.py b/plugins/sitemap/__init__.py new file mode 100644 index 0000000..6523d3a --- /dev/null +++ b/plugins/sitemap/__init__.py @@ -0,0 +1 @@ +from .sitemap import * \ No newline at end of file diff --git a/plugins/sitemap/__init__.pyc b/plugins/sitemap/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1581b2b5d4a4cf2e1f01bd351b9fbb6a7f56177c GIT binary patch literal 190 zcmZSn%*!>QOd~d#0ScIav;z + +""" + +XML_URL = """ + +{0}/{1} +{2} +{3} +{4} + +""" + +XML_FOOTER = """ + +""" + + +def format_date(date): + if date.tzinfo: + tz = date.strftime('%z') + tz = tz[:-2] + ':' + tz[-2:] + else: + tz = "-00:00" + return date.strftime("%Y-%m-%dT%H:%M:%S") + tz + + +class SitemapGenerator(object): + + def __init__(self, context, settings, path, theme, output_path, *null): + + self.output_path = output_path + self.context = context + self.now = datetime.now() + self.siteurl = settings.get('SITEURL') + + self.default_timezone = settings.get('TIMEZONE', 'UTC') + self.timezone = getattr(self, 'timezone', self.default_timezone) + self.timezone = timezone(self.timezone) + + self.format = 'xml' + + self.changefreqs = { + 'articles': 'monthly', + 'indexes': 'daily', + 'pages': 'monthly' + } + + self.priorities = { + 'articles': 0.5, + 'indexes': 0.5, + 'pages': 0.5 + } + + self.sitemapExclude = [] + + config = settings.get('SITEMAP', {}) + + if not isinstance(config, dict): + warning("sitemap plugin: the SITEMAP setting must be a dict") + else: + fmt = config.get('format') + pris = config.get('priorities') + chfreqs = config.get('changefreqs') + self.sitemapExclude = config.get('exclude', []) + + if fmt not in ('xml', 'txt'): + warning("sitemap plugin: SITEMAP['format'] must be `txt' or `xml'") + warning("sitemap plugin: Setting SITEMAP['format'] on `xml'") + elif fmt == 'txt': + self.format = fmt + return + + valid_keys = ('articles', 'indexes', 'pages') + valid_chfreqs = ('always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never') + + if isinstance(pris, dict): + # We use items for Py3k compat. .iteritems() otherwise + for k, v in pris.items(): + if k in valid_keys and not isinstance(v, (int, float)): + default = self.priorities[k] + warning("sitemap plugin: priorities must be numbers") + warning("sitemap plugin: setting SITEMAP['priorities']" + "['{0}'] on {1}".format(k, default)) + pris[k] = default + self.priorities.update(pris) + elif pris is not None: + warning("sitemap plugin: SITEMAP['priorities'] must be a dict") + warning("sitemap plugin: using the default values") + + if isinstance(chfreqs, dict): + # .items() for py3k compat. + for k, v in chfreqs.items(): + if k in valid_keys and v not in valid_chfreqs: + default = self.changefreqs[k] + warning("sitemap plugin: invalid changefreq `{0}'".format(v)) + warning("sitemap plugin: setting SITEMAP['changefreqs']" + "['{0}'] on '{1}'".format(k, default)) + chfreqs[k] = default + self.changefreqs.update(chfreqs) + elif chfreqs is not None: + warning("sitemap plugin: SITEMAP['changefreqs'] must be a dict") + warning("sitemap plugin: using the default values") + + def write_url(self, page, fd): + + if getattr(page, 'status', 'published') != 'published': + return + + if getattr(page, 'private', 'False') == 'True': + return + + # We can disable categories/authors/etc by using False instead of '' + if not page.save_as: + return + + page_path = os.path.join(self.output_path, page.save_as) + if not os.path.exists(page_path): + return + + lastdate = getattr(page, 'date', self.now) + try: + lastdate = self.get_date_modified(page, lastdate) + except ValueError: + warning("sitemap plugin: " + page.save_as + " has invalid modification date,") + warning("sitemap plugin: using date value as lastmod.") + lastmod = format_date(lastdate) + + if isinstance(page, contents.Article): + pri = self.priorities['articles'] + chfreq = self.changefreqs['articles'] + elif isinstance(page, contents.Page): + pri = self.priorities['pages'] + chfreq = self.changefreqs['pages'] + else: + pri = self.priorities['indexes'] + chfreq = self.changefreqs['indexes'] + + pageurl = '' if page.url == 'index.html' else page.url + + # Exclude URLs from the sitemap: + if self.format == 'xml': + flag = False + for regstr in self.sitemapExclude: + if re.match(regstr, pageurl): + flag = True + break + if not flag: + fd.write(XML_URL.format(self.siteurl, pageurl, lastmod, chfreq, pri)) + else: + fd.write(self.siteurl + '/' + pageurl + '\n') + + def get_date_modified(self, page, default): + if hasattr(page, 'modified'): + if isinstance(page.modified, datetime): + return page.modified + return get_date(page.modified) + else: + return default + + def set_url_wrappers_modification_date(self, wrappers): + for (wrapper, articles) in wrappers: + lastmod = datetime.min.replace(tzinfo=self.timezone) + for article in articles: + lastmod = max(lastmod, article.date.replace(tzinfo=self.timezone)) + try: + modified = self.get_date_modified(article, datetime.min).replace(tzinfo=self.timezone) + lastmod = max(lastmod, modified) + except ValueError: + # Supressed: user will be notified. + pass + setattr(wrapper, 'modified', str(lastmod)) + + def generate_output(self, writer): + path = os.path.join(self.output_path, 'sitemap.{0}'.format(self.format)) + + print("######################################################################") + print(self.context) + pages = self.context['pages'] \ + + self.context['articles'] \ + + [c for (c, a) in self.context['categories']] \ + + [t for (t, a) in self.context['tags']] \ + + [a for (a, b) in self.context['authors']] + + self.set_url_wrappers_modification_date(self.context['categories']) + self.set_url_wrappers_modification_date(self.context['tags']) + self.set_url_wrappers_modification_date(self.context['authors']) + + for article in self.context['articles']: + pages += article.translations + + info('writing {0}'.format(path)) + + with open(path, 'w', encoding='utf-8') as fd: + + if self.format == 'xml': + fd.write(XML_HEADER) + else: + fd.write(TXT_HEADER.format(self.siteurl)) + + FakePage = collections.namedtuple('FakePage', + ['status', + 'date', + 'url', + 'save_as']) + + for standard_page_url in ['index.html', + 'archives.html', + 'tags.html', + 'categories.html']: + fake = FakePage(status='published', + date=self.now, + url=standard_page_url, + save_as=standard_page_url) + self.write_url(fake, fd) + + # add template pages + # We use items for Py3k compat. .iteritems() otherwise + for path, template_page_url in self.context['TEMPLATE_PAGES'].items(): + + # don't add duplicate entry for index page + if template_page_url == 'index.html': + continue + + fake = FakePage(status='published', + date=self.now, + url=template_page_url, + save_as=template_page_url) + self.write_url(fake, fd) + + for page in pages: + self.write_url(page, fd) + + if self.format == 'xml': + fd.write(XML_FOOTER) + + +def get_generators(generators): + return SitemapGenerator + + +def register(): + signals.get_generators.connect(get_generators) diff --git a/plugins/sitemap/sitemap.pyc b/plugins/sitemap/sitemap.pyc new file mode 100644 index 0000000000000000000000000000000000000000..631bac15e50db775ca4b52c12a6e0aa0047bb0b4 GIT binary patch literal 8341 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, Drafts, Tutorials, ... -

This page contains tutorials, rough drafts I work on. The articles in here aren't necessarily finished and some of them aren't open to discussion, because they are work in progress (or chaos). Some of them might be turned into self-contained blog posts, some of them will never get out of this dungeon.

-{% endblock %} +

nothing to see here

dfsafsdf
    -{% for page in pages | sort(attribute='title') %} -
  • {{ page.title }}
  • -{% endfor %} + {% for article in (articles_page.object_list if articles_page else articles) %} +
    +

    {{ article.title }}

    + {{ article.url}} + {% endfor %}
+{% endblock %}