From ca94afc07df55cb7fc6fe3b4f3011877b7881195 Mon Sep 17 00:00:00 2001
From: Agustin Henze
Date: Wed, 20 Nov 2013 16:58:50 -0300
Subject: Imported Upstream version 6.2.1
---
nikola/plugins/__init__.py | 3 +-
nikola/plugins/basic_import.py | 166 +++++++
nikola/plugins/command/__init__.py | 25 +
nikola/plugins/command/auto.plugin | 9 +
nikola/plugins/command/auto.py | 103 ++++
nikola/plugins/command/bootswatch_theme.plugin | 10 +
nikola/plugins/command/bootswatch_theme.py | 103 ++++
nikola/plugins/command/check.plugin | 10 +
nikola/plugins/command/check.py | 204 ++++++++
nikola/plugins/command/console.plugin | 9 +
nikola/plugins/command/console.py | 110 ++++
nikola/plugins/command/deploy.plugin | 9 +
nikola/plugins/command/deploy.py | 141 ++++++
nikola/plugins/command/import_blogger.plugin | 10 +
nikola/plugins/command/import_blogger.py | 229 +++++++++
nikola/plugins/command/import_feed.plugin | 10 +
nikola/plugins/command/import_feed.py | 197 ++++++++
nikola/plugins/command/import_wordpress.plugin | 10 +
nikola/plugins/command/import_wordpress.py | 443 +++++++++++++++++
nikola/plugins/command/init.plugin | 9 +
nikola/plugins/command/init.py | 137 +++++
nikola/plugins/command/install_plugin.plugin | 10 +
nikola/plugins/command/install_plugin.py | 185 +++++++
nikola/plugins/command/install_theme.plugin | 10 +
nikola/plugins/command/install_theme.py | 163 ++++++
nikola/plugins/command/mincss.plugin | 10 +
nikola/plugins/command/mincss.py | 75 +++
nikola/plugins/command/new_post.plugin | 10 +
nikola/plugins/command/new_post.py | 291 +++++++++++
nikola/plugins/command/planetoid.plugin | 9 +
nikola/plugins/command/planetoid/__init__.py | 289 +++++++++++
nikola/plugins/command/serve.plugin | 10 +
nikola/plugins/command/serve.py | 153 ++++++
nikola/plugins/command/version.plugin | 9 +
nikola/plugins/command/version.py | 44 ++
nikola/plugins/command_bootswatch_theme.plugin | 10 -
nikola/plugins/command_bootswatch_theme.py | 96 ----
nikola/plugins/command_check.plugin | 10 -
nikola/plugins/command_check.py | 166 -------
nikola/plugins/command_console.plugin | 9 -
nikola/plugins/command_console.py | 105 ----
nikola/plugins/command_deploy.plugin | 9 -
nikola/plugins/command_deploy.py | 65 ---
nikola/plugins/command_import_blogger.plugin | 10 -
nikola/plugins/command_import_blogger.py | 308 ------------
nikola/plugins/command_import_wordpress.plugin | 10 -
nikola/plugins/command_import_wordpress.py | 439 ----------------
nikola/plugins/command_init.plugin | 9 -
nikola/plugins/command_init.py | 122 -----
nikola/plugins/command_install_theme.plugin | 10 -
nikola/plugins/command_install_theme.py | 105 ----
nikola/plugins/command_new_post.plugin | 10 -
nikola/plugins/command_new_post.py | 225 ---------
nikola/plugins/command_planetoid.plugin | 9 -
nikola/plugins/command_planetoid/__init__.py | 287 -----------
nikola/plugins/command_serve.plugin | 10 -
nikola/plugins/command_serve.py | 79 ---
nikola/plugins/compile/__init__.py | 0
nikola/plugins/compile/asciidoc.plugin | 10 +
nikola/plugins/compile/asciidoc.py | 65 +++
nikola/plugins/compile/bbcode.plugin | 10 +
nikola/plugins/compile/bbcode.py | 76 +++
nikola/plugins/compile/html.plugin | 10 +
nikola/plugins/compile/html.py | 58 +++
nikola/plugins/compile/ipynb.plugin | 10 +
nikola/plugins/compile/ipynb/README.txt | 44 ++
nikola/plugins/compile/ipynb/__init__.py | 100 ++++
nikola/plugins/compile/markdown.plugin | 10 +
nikola/plugins/compile/markdown/__init__.py | 88 ++++
nikola/plugins/compile/markdown/mdx_gist.py | 241 +++++++++
nikola/plugins/compile/markdown/mdx_nikola.py | 58 +++
nikola/plugins/compile/markdown/mdx_podcast.py | 87 ++++
nikola/plugins/compile/misaka.plugin | 10 +
nikola/plugins/compile/misaka.py | 81 +++
nikola/plugins/compile/pandoc.plugin | 10 +
nikola/plugins/compile/pandoc.py | 65 +++
nikola/plugins/compile/php.plugin | 10 +
nikola/plugins/compile/php.py | 62 +++
nikola/plugins/compile/rest.plugin | 10 +
nikola/plugins/compile/rest/__init__.py | 200 ++++++++
nikola/plugins/compile/rest/chart.plugin | 10 +
nikola/plugins/compile/rest/chart.py | 150 ++++++
nikola/plugins/compile/rest/doc.plugin | 10 +
nikola/plugins/compile/rest/doc.py | 88 ++++
nikola/plugins/compile/rest/gist.plugin | 10 +
nikola/plugins/compile/rest/gist.py | 84 ++++
nikola/plugins/compile/rest/listing.plugin | 10 +
nikola/plugins/compile/rest/listing.py | 109 ++++
nikola/plugins/compile/rest/media.plugin | 10 +
nikola/plugins/compile/rest/media.py | 63 +++
nikola/plugins/compile/rest/post_list.plugin | 9 +
nikola/plugins/compile/rest/post_list.py | 165 ++++++
nikola/plugins/compile/rest/slides.plugin | 10 +
nikola/plugins/compile/rest/slides.py | 67 +++
nikola/plugins/compile/rest/soundcloud.plugin | 10 +
nikola/plugins/compile/rest/soundcloud.py | 60 +++
nikola/plugins/compile/rest/vimeo.plugin | 7 +
nikola/plugins/compile/rest/vimeo.py | 125 +++++
nikola/plugins/compile/rest/youtube.plugin | 8 +
nikola/plugins/compile/rest/youtube.py | 81 +++
nikola/plugins/compile/textile.plugin | 10 +
nikola/plugins/compile/textile.py | 70 +++
nikola/plugins/compile/txt2tags.plugin | 10 +
nikola/plugins/compile/txt2tags.py | 70 +++
nikola/plugins/compile/wiki.plugin | 10 +
nikola/plugins/compile/wiki.py | 69 +++
nikola/plugins/compile_bbcode.plugin | 10 -
nikola/plugins/compile_bbcode.py | 76 ---
nikola/plugins/compile_html.plugin | 10 -
nikola/plugins/compile_html.py | 59 ---
nikola/plugins/compile_ipynb.plugin | 10 -
nikola/plugins/compile_ipynb/README.txt | 35 --
nikola/plugins/compile_ipynb/__init__.py | 100 ----
nikola/plugins/compile_markdown.plugin | 10 -
nikola/plugins/compile_markdown/__init__.py | 88 ----
nikola/plugins/compile_markdown/mdx_gist.py | 189 -------
nikola/plugins/compile_markdown/mdx_nikola.py | 56 ---
nikola/plugins/compile_markdown/mdx_podcast.py | 87 ----
nikola/plugins/compile_misaka.plugin | 10 -
nikola/plugins/compile_misaka/__init__.py | 82 ---
nikola/plugins/compile_rest.plugin | 10 -
nikola/plugins/compile_rest/__init__.py | 138 -----
nikola/plugins/compile_rest/dummy.py | 44 --
nikola/plugins/compile_rest/gist_directive.py | 56 ---
nikola/plugins/compile_rest/listing.py | 121 -----
nikola/plugins/compile_rest/slides.py | 65 ---
nikola/plugins/compile_rest/soundcloud.py | 50 --
nikola/plugins/compile_rest/vimeo.py | 118 -----
nikola/plugins/compile_rest/youtube.py | 69 ---
nikola/plugins/compile_textile.plugin | 10 -
nikola/plugins/compile_textile.py | 70 ---
nikola/plugins/compile_txt2tags.plugin | 10 -
nikola/plugins/compile_txt2tags.py | 73 ---
nikola/plugins/compile_wiki.plugin | 10 -
nikola/plugins/compile_wiki.py | 72 ---
nikola/plugins/loghandler/smtp.plugin | 9 +
nikola/plugins/loghandler/smtp.py | 54 ++
nikola/plugins/loghandler/stderr.plugin | 9 +
nikola/plugins/loghandler/stderr.py | 50 ++
nikola/plugins/task/__init__.py | 0
nikola/plugins/task/archive.plugin | 10 +
nikola/plugins/task/archive.py | 167 +++++++
nikola/plugins/task/build_less.plugin | 10 +
nikola/plugins/task/build_less.py | 99 ++++
nikola/plugins/task/build_sass.plugin | 9 +
nikola/plugins/task/build_sass.py | 117 +++++
nikola/plugins/task/bundles.plugin | 10 +
nikola/plugins/task/bundles.py | 116 +++++
nikola/plugins/task/copy_assets.plugin | 10 +
nikola/plugins/task/copy_assets.py | 89 ++++
nikola/plugins/task/copy_files.plugin | 10 +
nikola/plugins/task/copy_files.py | 55 ++
nikola/plugins/task/galleries.plugin | 10 +
nikola/plugins/task/galleries.py | 553 +++++++++++++++++++++
nikola/plugins/task/gzip.plugin | 10 +
nikola/plugins/task/gzip.py | 78 +++
nikola/plugins/task/indexes.plugin | 10 +
nikola/plugins/task/indexes.py | 167 +++++++
nikola/plugins/task/listings.plugin | 10 +
nikola/plugins/task/listings.py | 136 +++++
nikola/plugins/task/localsearch.plugin | 10 +
nikola/plugins/task/localsearch/MIT-LICENSE.txt | 20 +
nikola/plugins/task/localsearch/__init__.py | 106 ++++
.../localsearch/files/assets/css/img/loader.gif | Bin 0 -> 4178 bytes
.../localsearch/files/assets/css/img/search.png | Bin 0 -> 315 bytes
.../localsearch/files/assets/css/tipuesearch.css | 159 ++++++
.../localsearch/files/assets/js/tipuesearch.js | 384 ++++++++++++++
.../localsearch/files/assets/js/tipuesearch_set.js | 21 +
.../task/localsearch/files/tipue_search.html | 31 ++
nikola/plugins/task/mustache.plugin | 10 +
nikola/plugins/task/mustache/__init__.py | 182 +++++++
.../plugins/task/mustache/mustache-template.html | 29 ++
nikola/plugins/task/mustache/mustache.html | 34 ++
nikola/plugins/task/pages.plugin | 10 +
nikola/plugins/task/pages.py | 58 +++
nikola/plugins/task/posts.plugin | 10 +
nikola/plugins/task/posts.py | 66 +++
nikola/plugins/task/redirect.plugin | 10 +
nikola/plugins/task/redirect.py | 66 +++
nikola/plugins/task/rss.plugin | 10 +
nikola/plugins/task/rss.py | 91 ++++
nikola/plugins/task/sitemap.plugin | 10 +
nikola/plugins/task/sitemap/__init__.py | 172 +++++++
nikola/plugins/task/sources.plugin | 10 +
nikola/plugins/task/sources.py | 81 +++
nikola/plugins/task/tags.plugin | 10 +
nikola/plugins/task/tags.py | 324 ++++++++++++
nikola/plugins/task_archive.plugin | 10 -
nikola/plugins/task_archive.py | 147 ------
nikola/plugins/task_copy_assets.plugin | 10 -
nikola/plugins/task_copy_assets.py | 93 ----
nikola/plugins/task_copy_files.plugin | 10 -
nikola/plugins/task_copy_files.py | 59 ---
nikola/plugins/task_create_bundles.plugin | 10 -
nikola/plugins/task_create_bundles.py | 120 -----
nikola/plugins/task_indexes.plugin | 10 -
nikola/plugins/task_indexes.py | 141 ------
nikola/plugins/task_localsearch.plugin | 10 -
nikola/plugins/task_localsearch/MIT-LICENSE.txt | 20 -
nikola/plugins/task_localsearch/__init__.py | 102 ----
.../files/assets/css/img/expand.png | Bin 424 -> 0 bytes
.../task_localsearch/files/assets/css/img/link.png | Bin 463 -> 0 bytes
.../files/assets/css/img/loader.gif | Bin 4178 -> 0 bytes
.../files/assets/css/img/search.gif | Bin 208 -> 0 bytes
.../files/assets/css/tipuesearch.css | 232 ---------
.../files/assets/js/tipuesearch.js | 426 ----------------
.../files/assets/js/tipuesearch_set.js | 28 --
.../task_localsearch/files/tipue_search.html | 31 --
nikola/plugins/task_mustache.plugin | 10 -
nikola/plugins/task_mustache/__init__.py | 197 --------
.../plugins/task_mustache/mustache-template.html | 29 --
nikola/plugins/task_mustache/mustache.html | 36 --
nikola/plugins/task_redirect.plugin | 10 -
nikola/plugins/task_redirect.py | 76 ---
nikola/plugins/task_render_galleries.plugin | 10 -
nikola/plugins/task_render_galleries.py | 338 -------------
nikola/plugins/task_render_listings.plugin | 10 -
nikola/plugins/task_render_listings.py | 129 -----
nikola/plugins/task_render_pages.plugin | 10 -
nikola/plugins/task_render_pages.py | 64 ---
nikola/plugins/task_render_posts.plugin | 10 -
nikola/plugins/task_render_posts.py | 140 ------
nikola/plugins/task_render_rss.plugin | 10 -
nikola/plugins/task_render_rss.py | 72 ---
nikola/plugins/task_render_sources.plugin | 10 -
nikola/plugins/task_render_sources.py | 84 ----
nikola/plugins/task_render_tags.plugin | 10 -
nikola/plugins/task_render_tags.py | 243 ---------
nikola/plugins/task_sitemap.plugin | 10 -
nikola/plugins/task_sitemap/__init__.py | 105 ----
nikola/plugins/template/__init__.py | 0
nikola/plugins/template/jinja.plugin | 9 +
nikola/plugins/template/jinja.py | 102 ++++
nikola/plugins/template/mako.plugin | 9 +
nikola/plugins/template/mako.py | 115 +++++
nikola/plugins/template_jinja.plugin | 9 -
nikola/plugins/template_jinja.py | 76 ---
nikola/plugins/template_mako.plugin | 9 -
nikola/plugins/template_mako.py | 92 ----
239 files changed, 9800 insertions(+), 7101 deletions(-)
create mode 100644 nikola/plugins/basic_import.py
create mode 100644 nikola/plugins/command/__init__.py
create mode 100644 nikola/plugins/command/auto.plugin
create mode 100644 nikola/plugins/command/auto.py
create mode 100644 nikola/plugins/command/bootswatch_theme.plugin
create mode 100644 nikola/plugins/command/bootswatch_theme.py
create mode 100644 nikola/plugins/command/check.plugin
create mode 100644 nikola/plugins/command/check.py
create mode 100644 nikola/plugins/command/console.plugin
create mode 100644 nikola/plugins/command/console.py
create mode 100644 nikola/plugins/command/deploy.plugin
create mode 100644 nikola/plugins/command/deploy.py
create mode 100644 nikola/plugins/command/import_blogger.plugin
create mode 100644 nikola/plugins/command/import_blogger.py
create mode 100644 nikola/plugins/command/import_feed.plugin
create mode 100644 nikola/plugins/command/import_feed.py
create mode 100644 nikola/plugins/command/import_wordpress.plugin
create mode 100644 nikola/plugins/command/import_wordpress.py
create mode 100644 nikola/plugins/command/init.plugin
create mode 100644 nikola/plugins/command/init.py
create mode 100644 nikola/plugins/command/install_plugin.plugin
create mode 100644 nikola/plugins/command/install_plugin.py
create mode 100644 nikola/plugins/command/install_theme.plugin
create mode 100644 nikola/plugins/command/install_theme.py
create mode 100644 nikola/plugins/command/mincss.plugin
create mode 100644 nikola/plugins/command/mincss.py
create mode 100644 nikola/plugins/command/new_post.plugin
create mode 100644 nikola/plugins/command/new_post.py
create mode 100644 nikola/plugins/command/planetoid.plugin
create mode 100644 nikola/plugins/command/planetoid/__init__.py
create mode 100644 nikola/plugins/command/serve.plugin
create mode 100644 nikola/plugins/command/serve.py
create mode 100644 nikola/plugins/command/version.plugin
create mode 100644 nikola/plugins/command/version.py
delete mode 100644 nikola/plugins/command_bootswatch_theme.plugin
delete mode 100644 nikola/plugins/command_bootswatch_theme.py
delete mode 100644 nikola/plugins/command_check.plugin
delete mode 100644 nikola/plugins/command_check.py
delete mode 100644 nikola/plugins/command_console.plugin
delete mode 100644 nikola/plugins/command_console.py
delete mode 100644 nikola/plugins/command_deploy.plugin
delete mode 100644 nikola/plugins/command_deploy.py
delete mode 100644 nikola/plugins/command_import_blogger.plugin
delete mode 100644 nikola/plugins/command_import_blogger.py
delete mode 100644 nikola/plugins/command_import_wordpress.plugin
delete mode 100644 nikola/plugins/command_import_wordpress.py
delete mode 100644 nikola/plugins/command_init.plugin
delete mode 100644 nikola/plugins/command_init.py
delete mode 100644 nikola/plugins/command_install_theme.plugin
delete mode 100644 nikola/plugins/command_install_theme.py
delete mode 100644 nikola/plugins/command_new_post.plugin
delete mode 100644 nikola/plugins/command_new_post.py
delete mode 100644 nikola/plugins/command_planetoid.plugin
delete mode 100644 nikola/plugins/command_planetoid/__init__.py
delete mode 100644 nikola/plugins/command_serve.plugin
delete mode 100644 nikola/plugins/command_serve.py
create mode 100644 nikola/plugins/compile/__init__.py
create mode 100644 nikola/plugins/compile/asciidoc.plugin
create mode 100644 nikola/plugins/compile/asciidoc.py
create mode 100644 nikola/plugins/compile/bbcode.plugin
create mode 100644 nikola/plugins/compile/bbcode.py
create mode 100644 nikola/plugins/compile/html.plugin
create mode 100644 nikola/plugins/compile/html.py
create mode 100644 nikola/plugins/compile/ipynb.plugin
create mode 100644 nikola/plugins/compile/ipynb/README.txt
create mode 100644 nikola/plugins/compile/ipynb/__init__.py
create mode 100644 nikola/plugins/compile/markdown.plugin
create mode 100644 nikola/plugins/compile/markdown/__init__.py
create mode 100644 nikola/plugins/compile/markdown/mdx_gist.py
create mode 100644 nikola/plugins/compile/markdown/mdx_nikola.py
create mode 100644 nikola/plugins/compile/markdown/mdx_podcast.py
create mode 100644 nikola/plugins/compile/misaka.plugin
create mode 100644 nikola/plugins/compile/misaka.py
create mode 100644 nikola/plugins/compile/pandoc.plugin
create mode 100644 nikola/plugins/compile/pandoc.py
create mode 100644 nikola/plugins/compile/php.plugin
create mode 100644 nikola/plugins/compile/php.py
create mode 100644 nikola/plugins/compile/rest.plugin
create mode 100644 nikola/plugins/compile/rest/__init__.py
create mode 100644 nikola/plugins/compile/rest/chart.plugin
create mode 100644 nikola/plugins/compile/rest/chart.py
create mode 100644 nikola/plugins/compile/rest/doc.plugin
create mode 100644 nikola/plugins/compile/rest/doc.py
create mode 100644 nikola/plugins/compile/rest/gist.plugin
create mode 100644 nikola/plugins/compile/rest/gist.py
create mode 100644 nikola/plugins/compile/rest/listing.plugin
create mode 100644 nikola/plugins/compile/rest/listing.py
create mode 100644 nikola/plugins/compile/rest/media.plugin
create mode 100644 nikola/plugins/compile/rest/media.py
create mode 100644 nikola/plugins/compile/rest/post_list.plugin
create mode 100644 nikola/plugins/compile/rest/post_list.py
create mode 100644 nikola/plugins/compile/rest/slides.plugin
create mode 100644 nikola/plugins/compile/rest/slides.py
create mode 100644 nikola/plugins/compile/rest/soundcloud.plugin
create mode 100644 nikola/plugins/compile/rest/soundcloud.py
create mode 100644 nikola/plugins/compile/rest/vimeo.plugin
create mode 100644 nikola/plugins/compile/rest/vimeo.py
create mode 100644 nikola/plugins/compile/rest/youtube.plugin
create mode 100644 nikola/plugins/compile/rest/youtube.py
create mode 100644 nikola/plugins/compile/textile.plugin
create mode 100644 nikola/plugins/compile/textile.py
create mode 100644 nikola/plugins/compile/txt2tags.plugin
create mode 100644 nikola/plugins/compile/txt2tags.py
create mode 100644 nikola/plugins/compile/wiki.plugin
create mode 100644 nikola/plugins/compile/wiki.py
delete mode 100644 nikola/plugins/compile_bbcode.plugin
delete mode 100644 nikola/plugins/compile_bbcode.py
delete mode 100644 nikola/plugins/compile_html.plugin
delete mode 100644 nikola/plugins/compile_html.py
delete mode 100644 nikola/plugins/compile_ipynb.plugin
delete mode 100644 nikola/plugins/compile_ipynb/README.txt
delete mode 100644 nikola/plugins/compile_ipynb/__init__.py
delete mode 100644 nikola/plugins/compile_markdown.plugin
delete mode 100644 nikola/plugins/compile_markdown/__init__.py
delete mode 100644 nikola/plugins/compile_markdown/mdx_gist.py
delete mode 100644 nikola/plugins/compile_markdown/mdx_nikola.py
delete mode 100644 nikola/plugins/compile_markdown/mdx_podcast.py
delete mode 100644 nikola/plugins/compile_misaka.plugin
delete mode 100644 nikola/plugins/compile_misaka/__init__.py
delete mode 100644 nikola/plugins/compile_rest.plugin
delete mode 100644 nikola/plugins/compile_rest/__init__.py
delete mode 100644 nikola/plugins/compile_rest/dummy.py
delete mode 100644 nikola/plugins/compile_rest/gist_directive.py
delete mode 100644 nikola/plugins/compile_rest/listing.py
delete mode 100644 nikola/plugins/compile_rest/slides.py
delete mode 100644 nikola/plugins/compile_rest/soundcloud.py
delete mode 100644 nikola/plugins/compile_rest/vimeo.py
delete mode 100644 nikola/plugins/compile_rest/youtube.py
delete mode 100644 nikola/plugins/compile_textile.plugin
delete mode 100644 nikola/plugins/compile_textile.py
delete mode 100644 nikola/plugins/compile_txt2tags.plugin
delete mode 100644 nikola/plugins/compile_txt2tags.py
delete mode 100644 nikola/plugins/compile_wiki.plugin
delete mode 100644 nikola/plugins/compile_wiki.py
create mode 100644 nikola/plugins/loghandler/smtp.plugin
create mode 100644 nikola/plugins/loghandler/smtp.py
create mode 100644 nikola/plugins/loghandler/stderr.plugin
create mode 100644 nikola/plugins/loghandler/stderr.py
create mode 100644 nikola/plugins/task/__init__.py
create mode 100644 nikola/plugins/task/archive.plugin
create mode 100644 nikola/plugins/task/archive.py
create mode 100644 nikola/plugins/task/build_less.plugin
create mode 100644 nikola/plugins/task/build_less.py
create mode 100644 nikola/plugins/task/build_sass.plugin
create mode 100644 nikola/plugins/task/build_sass.py
create mode 100644 nikola/plugins/task/bundles.plugin
create mode 100644 nikola/plugins/task/bundles.py
create mode 100644 nikola/plugins/task/copy_assets.plugin
create mode 100644 nikola/plugins/task/copy_assets.py
create mode 100644 nikola/plugins/task/copy_files.plugin
create mode 100644 nikola/plugins/task/copy_files.py
create mode 100644 nikola/plugins/task/galleries.plugin
create mode 100644 nikola/plugins/task/galleries.py
create mode 100644 nikola/plugins/task/gzip.plugin
create mode 100644 nikola/plugins/task/gzip.py
create mode 100644 nikola/plugins/task/indexes.plugin
create mode 100644 nikola/plugins/task/indexes.py
create mode 100644 nikola/plugins/task/listings.plugin
create mode 100644 nikola/plugins/task/listings.py
create mode 100644 nikola/plugins/task/localsearch.plugin
create mode 100644 nikola/plugins/task/localsearch/MIT-LICENSE.txt
create mode 100644 nikola/plugins/task/localsearch/__init__.py
create mode 100644 nikola/plugins/task/localsearch/files/assets/css/img/loader.gif
create mode 100755 nikola/plugins/task/localsearch/files/assets/css/img/search.png
create mode 100755 nikola/plugins/task/localsearch/files/assets/css/tipuesearch.css
create mode 100644 nikola/plugins/task/localsearch/files/assets/js/tipuesearch.js
create mode 100644 nikola/plugins/task/localsearch/files/assets/js/tipuesearch_set.js
create mode 100755 nikola/plugins/task/localsearch/files/tipue_search.html
create mode 100644 nikola/plugins/task/mustache.plugin
create mode 100644 nikola/plugins/task/mustache/__init__.py
create mode 100644 nikola/plugins/task/mustache/mustache-template.html
create mode 100644 nikola/plugins/task/mustache/mustache.html
create mode 100644 nikola/plugins/task/pages.plugin
create mode 100644 nikola/plugins/task/pages.py
create mode 100644 nikola/plugins/task/posts.plugin
create mode 100644 nikola/plugins/task/posts.py
create mode 100644 nikola/plugins/task/redirect.plugin
create mode 100644 nikola/plugins/task/redirect.py
create mode 100644 nikola/plugins/task/rss.plugin
create mode 100644 nikola/plugins/task/rss.py
create mode 100644 nikola/plugins/task/sitemap.plugin
create mode 100644 nikola/plugins/task/sitemap/__init__.py
create mode 100644 nikola/plugins/task/sources.plugin
create mode 100644 nikola/plugins/task/sources.py
create mode 100644 nikola/plugins/task/tags.plugin
create mode 100644 nikola/plugins/task/tags.py
delete mode 100644 nikola/plugins/task_archive.plugin
delete mode 100644 nikola/plugins/task_archive.py
delete mode 100644 nikola/plugins/task_copy_assets.plugin
delete mode 100644 nikola/plugins/task_copy_assets.py
delete mode 100644 nikola/plugins/task_copy_files.plugin
delete mode 100644 nikola/plugins/task_copy_files.py
delete mode 100644 nikola/plugins/task_create_bundles.plugin
delete mode 100644 nikola/plugins/task_create_bundles.py
delete mode 100644 nikola/plugins/task_indexes.plugin
delete mode 100644 nikola/plugins/task_indexes.py
delete mode 100644 nikola/plugins/task_localsearch.plugin
delete mode 100644 nikola/plugins/task_localsearch/MIT-LICENSE.txt
delete mode 100644 nikola/plugins/task_localsearch/__init__.py
delete mode 100755 nikola/plugins/task_localsearch/files/assets/css/img/expand.png
delete mode 100755 nikola/plugins/task_localsearch/files/assets/css/img/link.png
delete mode 100644 nikola/plugins/task_localsearch/files/assets/css/img/loader.gif
delete mode 100644 nikola/plugins/task_localsearch/files/assets/css/img/search.gif
delete mode 100755 nikola/plugins/task_localsearch/files/assets/css/tipuesearch.css
delete mode 100644 nikola/plugins/task_localsearch/files/assets/js/tipuesearch.js
delete mode 100644 nikola/plugins/task_localsearch/files/assets/js/tipuesearch_set.js
delete mode 100755 nikola/plugins/task_localsearch/files/tipue_search.html
delete mode 100644 nikola/plugins/task_mustache.plugin
delete mode 100644 nikola/plugins/task_mustache/__init__.py
delete mode 100644 nikola/plugins/task_mustache/mustache-template.html
delete mode 100644 nikola/plugins/task_mustache/mustache.html
delete mode 100644 nikola/plugins/task_redirect.plugin
delete mode 100644 nikola/plugins/task_redirect.py
delete mode 100644 nikola/plugins/task_render_galleries.plugin
delete mode 100644 nikola/plugins/task_render_galleries.py
delete mode 100644 nikola/plugins/task_render_listings.plugin
delete mode 100644 nikola/plugins/task_render_listings.py
delete mode 100644 nikola/plugins/task_render_pages.plugin
delete mode 100644 nikola/plugins/task_render_pages.py
delete mode 100644 nikola/plugins/task_render_posts.plugin
delete mode 100644 nikola/plugins/task_render_posts.py
delete mode 100644 nikola/plugins/task_render_rss.plugin
delete mode 100644 nikola/plugins/task_render_rss.py
delete mode 100644 nikola/plugins/task_render_sources.plugin
delete mode 100644 nikola/plugins/task_render_sources.py
delete mode 100644 nikola/plugins/task_render_tags.plugin
delete mode 100644 nikola/plugins/task_render_tags.py
delete mode 100644 nikola/plugins/task_sitemap.plugin
delete mode 100644 nikola/plugins/task_sitemap/__init__.py
create mode 100644 nikola/plugins/template/__init__.py
create mode 100644 nikola/plugins/template/jinja.plugin
create mode 100644 nikola/plugins/template/jinja.py
create mode 100644 nikola/plugins/template/mako.plugin
create mode 100644 nikola/plugins/template/mako.py
delete mode 100644 nikola/plugins/template_jinja.plugin
delete mode 100644 nikola/plugins/template_jinja.py
delete mode 100644 nikola/plugins/template_mako.plugin
delete mode 100644 nikola/plugins/template_mako.py
(limited to 'nikola/plugins')
diff --git a/nikola/plugins/__init__.py b/nikola/plugins/__init__.py
index b1de7f1..139759b 100644
--- a/nikola/plugins/__init__.py
+++ b/nikola/plugins/__init__.py
@@ -1,3 +1,2 @@
+# -*- coding: utf-8 -*-
from __future__ import absolute_import
-
-from . import command_import_wordpress # NOQA
diff --git a/nikola/plugins/basic_import.py b/nikola/plugins/basic_import.py
new file mode 100644
index 0000000..e368fca
--- /dev/null
+++ b/nikola/plugins/basic_import.py
@@ -0,0 +1,166 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import unicode_literals, print_function
+import codecs
+import csv
+import datetime
+import os
+
+try:
+ from urlparse import urlparse
+except ImportError:
+ from urllib.parse import urlparse # NOQA
+
+from lxml import etree, html
+from mako.template import Template
+
+from nikola import utils
+
+links = {}
+
+
+class ImportMixin(object):
+ """Mixin with common used methods."""
+
+ name = "import_mixin"
+ needs_config = False
+ doc_usage = "[options] wordpress_export_file"
+ doc_purpose = "import a wordpress dump."
+ cmd_options = [
+ {
+ 'name': 'output_folder',
+ 'long': 'output-folder',
+ 'short': 'o',
+ 'default': 'new_site',
+ 'help': 'Location to write imported content.'
+ },
+ ]
+
+ def _execute(self, options={}, args=[]):
+ """Import a blog from an export into a Nikola site."""
+ raise NotImplementedError("Must be implemented by a subclass.")
+
+ @classmethod
+ def get_channel_from_file(cls, filename):
+ tree = etree.fromstring(cls.read_xml_file(filename))
+ channel = tree.find('channel')
+ return channel
+
+ @staticmethod
+ def configure_redirections(url_map):
+ redirections = []
+ for k, v in url_map.items():
+ if not k[-1] == '/':
+ k = k + '/'
+
+ # remove the initial "/" because src is a relative file path
+ src = (urlparse(k).path + 'index.html')[1:]
+ dst = (urlparse(v).path)
+ if src == 'index.html':
+ utils.LOGGER.warn("Can't do a redirect for: {0!r}".format(k))
+ else:
+ redirections.append((src, dst))
+
+ return redirections
+
+ def generate_base_site(self):
+ if not os.path.exists(self.output_folder):
+ os.system('nikola init ' + self.output_folder)
+ else:
+ self.import_into_existing_site = True
+ utils.LOGGER.notice('The folder {0} already exists - assuming that this is a '
+ 'already existing nikola site.'.format(self.output_folder))
+
+ filename = os.path.join(os.path.dirname(utils.__file__), 'conf.py.in')
+ # The 'strict_undefined=True' will give the missing symbol name if any,
+ # (ex: NameError: 'THEME' is not defined )
+ # for other errors from mako/runtime.py, you can add format_extensions=True ,
+ # then more info will be writen to *somefile* (most probably conf.py)
+ conf_template = Template(filename=filename, strict_undefined=True)
+
+ return conf_template
+
+ @staticmethod
+ def populate_context(channel):
+ raise NotImplementedError("Must be implemented by a subclass.")
+
+ @classmethod
+ def transform_content(cls, content):
+ return content
+
+ @classmethod
+ def write_content(cls, filename, content):
+ doc = html.document_fromstring(content)
+ doc.rewrite_links(replacer)
+
+ utils.makedirs(os.path.dirname(filename))
+ with open(filename, "wb+") as fd:
+ fd.write(html.tostring(doc, encoding='utf8'))
+
+ @staticmethod
+ def write_metadata(filename, title, slug, post_date, description, tags):
+ if not description:
+ description = ""
+
+ utils.makedirs(os.path.dirname(filename))
+ with codecs.open(filename, "w+", "utf8") as fd:
+ fd.write('{0}\n'.format(title))
+ fd.write('{0}\n'.format(slug))
+ fd.write('{0}\n'.format(post_date))
+ fd.write('{0}\n'.format(','.join(tags)))
+ fd.write('\n')
+ fd.write('{0}\n'.format(description))
+
+ @staticmethod
+ def write_urlmap_csv(output_file, url_map):
+ utils.makedirs(os.path.dirname(output_file))
+ with codecs.open(output_file, 'w+', 'utf8') as fd:
+ csv_writer = csv.writer(fd)
+ for item in url_map.items():
+ csv_writer.writerow(item)
+
+ def get_configuration_output_path(self):
+ if not self.import_into_existing_site:
+ filename = 'conf.py'
+ else:
+ filename = 'conf.py.{name}-{time}'.format(
+ time=datetime.datetime.now().strftime('%Y%m%d_%H%M%S'),
+ name=self.name)
+ config_output_path = os.path.join(self.output_folder, filename)
+ utils.LOGGER.notice('Configuration will be written to: {0}'.format(config_output_path))
+
+ return config_output_path
+
+ @staticmethod
+ def write_configuration(filename, rendered_template):
+ utils.makedirs(os.path.dirname(filename))
+ with codecs.open(filename, 'w+', 'utf8') as fd:
+ fd.write(rendered_template)
+
+
+def replacer(dst):
+ return links.get(dst, dst)
diff --git a/nikola/plugins/command/__init__.py b/nikola/plugins/command/__init__.py
new file mode 100644
index 0000000..9be4d63
--- /dev/null
+++ b/nikola/plugins/command/__init__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/nikola/plugins/command/auto.plugin b/nikola/plugins/command/auto.plugin
new file mode 100644
index 0000000..87939b2
--- /dev/null
+++ b/nikola/plugins/command/auto.plugin
@@ -0,0 +1,9 @@
+[Core]
+Name = auto
+Module = auto
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.2
+Website = http://getnikola.com
+Description = Automatically detect site changes, rebuild and optionally refresh a browser.
diff --git a/nikola/plugins/command/auto.py b/nikola/plugins/command/auto.py
new file mode 100644
index 0000000..cb726d9
--- /dev/null
+++ b/nikola/plugins/command/auto.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function, unicode_literals
+
+import codecs
+import json
+import os
+import subprocess
+
+from nikola.plugin_categories import Command
+from nikola.utils import req_missing
+
+GUARDFILE = """#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from livereload.task import Task
+import json
+import subprocess
+
+def f():
+ import subprocess
+ subprocess.call(("nikola", "build"))
+
+fdata = json.loads('''{0}''')
+
+for watch in fdata:
+ Task.add(watch, f)
+"""
+
+
+class Auto(Command):
+ """Start debugging console."""
+ name = "auto"
+ doc_purpose = "automatically detect site changes, rebuild and optionally refresh a browser"
+ cmd_options = [
+ {
+ 'name': 'browser',
+ 'short': 'b',
+ 'type': bool,
+ 'help': 'Start a web browser.',
+ 'default': False,
+ },
+ {
+ 'name': 'port',
+ 'short': 'p',
+ 'long': 'port',
+ 'default': 8000,
+ 'type': int,
+ 'help': 'Port nummber (default: 8000)',
+ },
+ ]
+
+ def _execute(self, options, args):
+ """Start the watcher."""
+ try:
+ from livereload.server import start
+ except ImportError:
+ req_missing(['livereload'], 'use the "auto" command')
+ return
+
+ # Run an initial build so we are uptodate
+ subprocess.call(("nikola", "build"))
+
+ port = options and options.get('port')
+
+ # Create a Guardfile
+ with codecs.open("Guardfile", "wb+", "utf8") as guardfile:
+ l = ["conf.py", "themes", "templates", self.site.config['GALLERY_PATH']]
+ for item in self.site.config['post_pages']:
+ l.append(os.path.dirname(item[0]))
+ for item in self.site.config['FILES_FOLDERS']:
+ l.append(os.path.dirname(item))
+ data = GUARDFILE.format(json.dumps(l))
+ guardfile.write(data)
+
+ out_folder = self.site.config['OUTPUT_FOLDER']
+
+ os.chmod("Guardfile", 0o755)
+
+ start(port, out_folder, options and options.get('browser'))
diff --git a/nikola/plugins/command/bootswatch_theme.plugin b/nikola/plugins/command/bootswatch_theme.plugin
new file mode 100644
index 0000000..7091310
--- /dev/null
+++ b/nikola/plugins/command/bootswatch_theme.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = bootswatch_theme
+Module = bootswatch_theme
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Given a swatch name and a parent theme, creates a custom theme.
+
diff --git a/nikola/plugins/command/bootswatch_theme.py b/nikola/plugins/command/bootswatch_theme.py
new file mode 100644
index 0000000..eb27f94
--- /dev/null
+++ b/nikola/plugins/command/bootswatch_theme.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+import os
+
+try:
+ import requests
+except ImportError:
+ requests = None # NOQA
+
+from nikola.plugin_categories import Command
+from nikola import utils
+
+LOGGER = utils.get_logger('bootswatch_theme', utils.STDERR_HANDLER)
+
+
+class CommandBootswatchTheme(Command):
+ """Given a swatch name from bootswatch.com and a parent theme, creates a custom theme."""
+
+ name = "bootswatch_theme"
+ doc_usage = "[options]"
+ doc_purpose = "given a swatch name from bootswatch.com and a parent theme, creates a custom"\
+ " theme"
+ cmd_options = [
+ {
+ 'name': 'name',
+ 'short': 'n',
+ 'long': 'name',
+ 'default': 'custom',
+ 'type': str,
+ 'help': 'New theme name (default: custom)',
+ },
+ {
+ 'name': 'swatch',
+ 'short': 's',
+ 'default': 'slate',
+ 'type': str,
+ 'help': 'Name of the swatch from bootswatch.com.'
+ },
+ {
+ 'name': 'parent',
+ 'short': 'p',
+ 'long': 'parent',
+ 'default': 'bootstrap3',
+ 'help': 'Parent theme name (default: bootstrap3)',
+ },
+ ]
+
+ def _execute(self, options, args):
+ """Given a swatch name and a parent theme, creates a custom theme."""
+ if requests is None:
+ utils.req_missing(['requests'], 'install Bootswatch themes')
+
+ name = options['name']
+ swatch = options['swatch']
+ parent = options['parent']
+ version = ''
+
+ # See if we need bootswatch for bootstrap v2 or v3
+ themes = utils.get_theme_chain(parent)
+ if 'bootstrap3' not in themes:
+ version = '2'
+ elif 'bootstrap' not in themes:
+ LOGGER.warn('"bootswatch_theme" only makes sense for themes that use bootstrap')
+
+ LOGGER.notice("Creating '{0}' theme from '{1}' and '{2}'".format(name, swatch, parent))
+ utils.makedirs(os.path.join('themes', name, 'assets', 'css'))
+ for fname in ('bootstrap.min.css', 'bootstrap.css'):
+ url = '/'.join(('http://bootswatch.com', version, swatch, fname))
+ LOGGER.notice("Downloading: " + url)
+ data = requests.get(url).text
+ with open(os.path.join('themes', name, 'assets', 'css', fname),
+ 'wb+') as output:
+ output.write(data.encode('utf-8'))
+
+ with open(os.path.join('themes', name, 'parent'), 'wb+') as output:
+ output.write(parent.encode('utf-8'))
+ LOGGER.notice('Theme created. Change the THEME setting to "{0}" to use '
+ 'it.'.format(name))
diff --git a/nikola/plugins/command/check.plugin b/nikola/plugins/command/check.plugin
new file mode 100644
index 0000000..8ceda5f
--- /dev/null
+++ b/nikola/plugins/command/check.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = check
+Module = check
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Check the generated site
+
diff --git a/nikola/plugins/command/check.py b/nikola/plugins/command/check.py
new file mode 100644
index 0000000..5c7e49a
--- /dev/null
+++ b/nikola/plugins/command/check.py
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+import os
+import re
+import sys
+try:
+ from urllib import unquote
+ from urlparse import urlparse
+except ImportError:
+ from urllib.parse import unquote, urlparse # NOQA
+
+import lxml.html
+
+from nikola.plugin_categories import Command
+from nikola.utils import get_logger
+
+
+class CommandCheck(Command):
+ """Check the generated site."""
+
+ name = "check"
+ logger = None
+
+ doc_usage = "-l [--find-sources] | -f"
+ doc_purpose = "check links and files in the generated site"
+ cmd_options = [
+ {
+ 'name': 'links',
+ 'short': 'l',
+ 'long': 'check-links',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Check for dangling links',
+ },
+ {
+ 'name': 'files',
+ 'short': 'f',
+ 'long': 'check-files',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Check for unknown files',
+ },
+ {
+ 'name': 'clean',
+ 'long': 'clean-files',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Remove all unknown files, use with caution',
+ },
+ {
+ 'name': 'find_sources',
+ 'long': 'find-sources',
+ 'type': bool,
+ 'default': False,
+ 'help': 'List possible source files for files with broken links.',
+ },
+ ]
+
+ def _execute(self, options, args):
+ """Check the generated site."""
+
+ self.logger = get_logger('check', self.site.loghandlers)
+
+ if not options['links'] and not options['files'] and not options['clean']:
+ print(self.help())
+ return False
+ if options['links']:
+ failure = self.scan_links(options['find_sources'])
+ if options['files']:
+ failure = self.scan_files()
+ if options['clean']:
+ failure = self.clean_files()
+ if failure:
+ sys.exit(1)
+
+ existing_targets = set([])
+
+ def analyze(self, task, find_sources=False):
+ rv = False
+ self.whitelist = [re.compile(x) for x in self.site.config['LINK_CHECK_WHITELIST']]
+ try:
+ filename = task.split(":")[-1]
+ d = lxml.html.fromstring(open(filename).read())
+ for l in d.iterlinks():
+ target = l[0].attrib[l[1]]
+ if target == "#":
+ continue
+ parsed = urlparse(target)
+ if parsed.scheme or target.startswith('//'):
+ continue
+ if parsed.fragment:
+ target = target.split('#')[0]
+ target_filename = os.path.abspath(
+ os.path.join(os.path.dirname(filename), unquote(target)))
+ if any(re.match(x, target_filename) for x in self.whitelist):
+ continue
+ elif target_filename not in self.existing_targets:
+ if os.path.exists(target_filename):
+ self.existing_targets.add(target_filename)
+ else:
+ rv = True
+ self.logger.warn("Broken link in {0}: ".format(filename), target)
+ if find_sources:
+ self.logger.warn("Possible sources:")
+ self.logger.warn(os.popen('nikola list --deps ' + task, 'r').read())
+ self.logger.warn("===============================\n")
+ except Exception as exc:
+ self.logger.error("Error with:", filename, exc)
+ return rv
+
+ def scan_links(self, find_sources=False):
+ self.logger.notice("Checking Links:")
+ self.logger.notice("===============")
+ failure = False
+ for task in os.popen('nikola list --all', 'r').readlines():
+ task = task.strip()
+ if task.split(':')[0] in (
+ 'render_tags', 'render_archive',
+ 'render_galleries', 'render_indexes',
+ 'render_pages'
+ 'render_site') and '.html' in task:
+ if self.analyze(task, find_sources):
+ failure = True
+ if not failure:
+ self.logger.notice("All links checked.")
+ return failure
+
+ def scan_files(self):
+ failure = False
+ self.logger.notice("Checking Files:")
+ self.logger.notice("===============\n")
+ only_on_output, only_on_input = self.real_scan_files()
+
+ # Ignore folders
+ only_on_output = [p for p in only_on_output if not os.path.isdir(p)]
+ only_on_input = [p for p in only_on_input if not os.path.isdir(p)]
+
+ if only_on_output:
+ only_on_output.sort()
+ self.logger.warn("Files from unknown origins:")
+ for f in only_on_output:
+ self.logger.warn(f)
+ failure = True
+ if only_on_input:
+ only_on_input.sort()
+ self.logger.warn("Files not generated:")
+ for f in only_on_input:
+ self.logger.warn(f)
+ if not failure:
+ self.logger.notice("All files checked.")
+ return failure
+
+ def clean_files(self):
+ only_on_output, _ = self.real_scan_files()
+ for f in only_on_output:
+ os.unlink(f)
+ return True
+
+ def real_scan_files(self):
+ task_fnames = set([])
+ real_fnames = set([])
+ output_folder = self.site.config['OUTPUT_FOLDER']
+ # First check that all targets are generated in the right places
+ for task in os.popen('nikola list --all', 'r').readlines():
+ task = task.strip()
+ if output_folder in task and ':' in task:
+ fname = task.split(':', 1)[-1]
+ task_fnames.add(fname)
+ # And now check that there are no non-target files
+ for root, dirs, files in os.walk(output_folder):
+ for src_name in files:
+ fname = os.path.join(root, src_name)
+ real_fnames.add(fname)
+
+ only_on_output = list(real_fnames - task_fnames)
+
+ only_on_input = list(task_fnames - real_fnames)
+
+ return (only_on_output, only_on_input)
diff --git a/nikola/plugins/command/console.plugin b/nikola/plugins/command/console.plugin
new file mode 100644
index 0000000..a2be9ca
--- /dev/null
+++ b/nikola/plugins/command/console.plugin
@@ -0,0 +1,9 @@
+[Core]
+Name = console
+Module = console
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Start a debugging python console
diff --git a/nikola/plugins/command/console.py b/nikola/plugins/command/console.py
new file mode 100644
index 0000000..fe17dfc
--- /dev/null
+++ b/nikola/plugins/command/console.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function, unicode_literals
+
+import os
+
+from nikola import __version__
+from nikola.plugin_categories import Command
+from nikola.utils import get_logger, STDERR_HANDLER
+
+LOGGER = get_logger('console', STDERR_HANDLER)
+
+
+class Console(Command):
+ """Start debugging console."""
+ name = "console"
+ shells = ['ipython', 'bpython', 'plain']
+ doc_purpose = "Start an interactive Python (IPython->bpython->plain) console with access to your site and configuration"
+ header = "Nikola v" + __version__ + " -- {0} Console (conf = configuration, SITE = site engine)"
+
+ def ipython(self):
+ """IPython shell."""
+ from nikola import Nikola
+ try:
+ import conf
+ except ImportError:
+ LOGGER.error("No configuration found, cannot run the console.")
+ else:
+ import IPython
+ SITE = Nikola(**conf.__dict__)
+ SITE.scan_posts()
+ IPython.embed(header=self.header.format('IPython'))
+
+ def bpython(self):
+ """bpython shell."""
+ from nikola import Nikola
+ try:
+ import conf
+ except ImportError:
+ LOGGER.error("No configuration found, cannot run the console.")
+ else:
+ import bpython
+ SITE = Nikola(**conf.__dict__)
+ SITE.scan_posts()
+ gl = {'conf': conf, 'SITE': SITE, 'Nikola': Nikola}
+ bpython.embed(banner=self.header.format(
+ 'bpython (Slightly Deprecated)'), locals_=gl)
+
+ def plain(self):
+ """Plain Python shell."""
+ from nikola import Nikola
+ try:
+ import conf
+ SITE = Nikola(**conf.__dict__)
+ SITE.scan_posts()
+ gl = {'conf': conf, 'SITE': SITE, 'Nikola': Nikola}
+ except ImportError:
+ LOGGER.error("No configuration found, cannot run the console.")
+ else:
+ import code
+ try:
+ import readline
+ except ImportError:
+ pass
+ else:
+ import rlcompleter
+ readline.set_completer(rlcompleter.Completer(gl).complete)
+ readline.parse_and_bind("tab:complete")
+
+ pythonrc = os.environ.get("PYTHONSTARTUP")
+ if pythonrc and os.path.isfile(pythonrc):
+ try:
+ execfile(pythonrc) # NOQA
+ except NameError:
+ pass
+
+ code.interact(local=gl, banner=self.header.format('Python'))
+
+ def _execute(self, options, args):
+ """Start the console."""
+ for shell in self.shells:
+ try:
+ return getattr(self, shell)()
+ except ImportError:
+ pass
+ raise ImportError
diff --git a/nikola/plugins/command/deploy.plugin b/nikola/plugins/command/deploy.plugin
new file mode 100644
index 0000000..10cc796
--- /dev/null
+++ b/nikola/plugins/command/deploy.plugin
@@ -0,0 +1,9 @@
+[Core]
+Name = deploy
+Module = deploy
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Deploy the site
diff --git a/nikola/plugins/command/deploy.py b/nikola/plugins/command/deploy.py
new file mode 100644
index 0000000..efb909d
--- /dev/null
+++ b/nikola/plugins/command/deploy.py
@@ -0,0 +1,141 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+from ast import literal_eval
+import codecs
+from datetime import datetime
+import os
+import sys
+import subprocess
+import time
+import pytz
+
+from blinker import signal
+
+from nikola.plugin_categories import Command
+from nikola.utils import remove_file, get_logger
+
+
+class Deploy(Command):
+ """Deploy site. """
+ name = "deploy"
+
+ doc_usage = ""
+ doc_purpose = "deploy the site"
+
+ logger = None
+
+ def _execute(self, command, args):
+ self.logger = get_logger('deploy', self.site.loghandlers)
+ # Get last successful deploy date
+ timestamp_path = os.path.join(self.site.config['CACHE_FOLDER'], 'lastdeploy')
+ if self.site.config['COMMENT_SYSTEM_ID'] == 'nikolademo':
+ self.logger.warn("\nWARNING WARNING WARNING WARNING\n"
+ "You are deploying using the nikolademo Disqus account.\n"
+ "That means you will not be able to moderate the comments in your own site.\n"
+ "And is probably not what you want to do.\n"
+ "Think about it for 5 seconds, I'll wait :-)\n\n")
+ time.sleep(5)
+
+ deploy_drafts = self.site.config.get('DEPLOY_DRAFTS', True)
+ deploy_future = self.site.config.get('DEPLOY_FUTURE', False)
+ if not (deploy_drafts and deploy_future):
+ # Remove drafts and future posts
+ out_dir = self.site.config['OUTPUT_FOLDER']
+ undeployed_posts = []
+ self.site.scan_posts()
+ for post in self.site.timeline:
+ if (not deploy_drafts and post.is_draft) or \
+ (not deploy_future and post.publish_later):
+ remove_file(os.path.join(out_dir, post.destination_path()))
+ remove_file(os.path.join(out_dir, post.source_path))
+ undeployed_posts.append(post)
+
+ for command in self.site.config['DEPLOY_COMMANDS']:
+ self.logger.notice("==> {0}".format(command))
+ try:
+ subprocess.check_call(command, shell=True)
+ except subprocess.CalledProcessError as e:
+ self.logger.error('Failed deployment — command {0} '
+ 'returned {1}'.format(e.cmd, e.returncode))
+ sys.exit(e.returncode)
+
+ self.logger.notice("Successful deployment")
+ if self.site.config['TIMEZONE'] is not None:
+ tzinfo = pytz.timezone(self.site.config['TIMEZONE'])
+ else:
+ tzinfo = pytz.UTC
+ try:
+ with open(timestamp_path, 'rb') as inf:
+ last_deploy = literal_eval(inf.read().strip())
+ # this might ignore DST
+ last_deploy = last_deploy.replace(tzinfo=tzinfo)
+ clean = False
+ except Exception:
+ last_deploy = datetime(1970, 1, 1).replace(tzinfo=tzinfo)
+ clean = True
+
+ new_deploy = datetime.now()
+ self._emit_deploy_event(last_deploy, new_deploy, clean, undeployed_posts)
+
+ # Store timestamp of successful deployment
+ with codecs.open(timestamp_path, 'wb+', 'utf8') as outf:
+ outf.write(repr(new_deploy))
+
+ def _emit_deploy_event(self, last_deploy, new_deploy, clean=False, undeployed=None):
+ """ Emit events for all timeline entries newer than last deploy.
+
+ last_deploy: datetime
+ Time stamp of the last successful deployment.
+
+ new_deploy: datetime
+ Time stamp of the current deployment.
+
+ clean: bool
+ True when it appears like deploy is being run after a clean.
+
+ """
+
+ if undeployed is None:
+ undeployed = []
+
+ event = {
+ 'last_deploy': last_deploy,
+ 'new_deploy': new_deploy,
+ 'clean': clean,
+ 'undeployed': undeployed
+ }
+
+ deployed = [
+ entry for entry in self.site.timeline
+ if entry.date > last_deploy and entry not in undeployed
+ ]
+
+ event['deployed'] = deployed
+
+ if len(deployed) > 0 or len(undeployed) > 0:
+ signal('deployed').send(event)
diff --git a/nikola/plugins/command/import_blogger.plugin b/nikola/plugins/command/import_blogger.plugin
new file mode 100644
index 0000000..91a7cb6
--- /dev/null
+++ b/nikola/plugins/command/import_blogger.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = import_blogger
+Module = import_blogger
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.2
+Website = http://getnikola.com
+Description = Import a blogger site from a XML dump.
+
diff --git a/nikola/plugins/command/import_blogger.py b/nikola/plugins/command/import_blogger.py
new file mode 100644
index 0000000..53618b4
--- /dev/null
+++ b/nikola/plugins/command/import_blogger.py
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import unicode_literals, print_function
+import datetime
+import os
+import time
+
+try:
+ from urlparse import urlparse
+except ImportError:
+ from urllib.parse import urlparse # NOQA
+
+try:
+ import feedparser
+except ImportError:
+ feedparser = None # NOQA
+
+from nikola.plugin_categories import Command
+from nikola import utils
+from nikola.utils import req_missing
+from nikola.plugins.basic_import import ImportMixin
+
+LOGGER = utils.get_logger('import_blogger', utils.STDERR_HANDLER)
+
+
+class CommandImportBlogger(Command, ImportMixin):
+ """Import a blogger dump."""
+
+ name = "import_blogger"
+ needs_config = False
+ doc_usage = "[options] blogger_export_file"
+ doc_purpose = "import a blogger dump"
+ cmd_options = ImportMixin.cmd_options + [
+ {
+ 'name': 'exclude_drafts',
+ 'long': 'no-drafts',
+ 'short': 'd',
+ 'default': False,
+ 'type': bool,
+ 'help': "Don't import drafts",
+ },
+ ]
+
+ def _execute(self, options, args):
+ """Import a Blogger blog from an export file into a Nikola site."""
+ # Parse the data
+ if feedparser is None:
+ req_missing(['feedparser'], 'import Blogger dumps')
+ return
+
+ if not args:
+ print(self.help())
+ return
+
+ options['filename'] = args[0]
+ self.blogger_export_file = options['filename']
+ self.output_folder = options['output_folder']
+ self.import_into_existing_site = False
+ self.exclude_drafts = options['exclude_drafts']
+ self.url_map = {}
+ channel = self.get_channel_from_file(self.blogger_export_file)
+ self.context = self.populate_context(channel)
+ conf_template = self.generate_base_site()
+ self.context['REDIRECTIONS'] = self.configure_redirections(
+ self.url_map)
+
+ self.import_posts(channel)
+ self.write_urlmap_csv(
+ os.path.join(self.output_folder, 'url_map.csv'), self.url_map)
+
+ conf_out_path = self.get_configuration_output_path()
+ # if it tracebacks here, look a comment in
+ # basic_import.Import_Mixin.generate_base_site
+ conf_termplate_render = conf_template.render(**self.context)
+ self.write_configuration(conf_out_path, conf_termplate_render)
+
+ @classmethod
+ def get_channel_from_file(cls, filename):
+ if not os.path.isfile(filename):
+ raise Exception("Missing file: %s" % filename)
+ return feedparser.parse(filename)
+
+ @staticmethod
+ def populate_context(channel):
+ # may need changes when the template conf.py.in changes
+ context = {}
+ context['DEFAULT_LANG'] = 'en' # blogger doesn't include the language
+ # in the dump
+ context['BLOG_TITLE'] = channel.feed.title
+
+ context['BLOG_DESCRIPTION'] = '' # Missing in the dump
+ context['SITE_URL'] = channel.feed.link
+ context['BLOG_EMAIL'] = channel.feed.author_detail.email
+ context['BLOG_AUTHOR'] = channel.feed.author_detail.name
+ context['POSTS'] = '''(
+ ("posts/*.txt", "posts", "post.tmpl"),
+ ("posts/*.rst", "posts", "post.tmpl"),
+ ("posts/*.html", "posts", "post.tmpl"),
+ )'''
+ context['PAGES'] = '''(
+ ("articles/*.txt", "articles", "story.tmpl"),
+ ("articles/*.rst", "articles", "story.tmpl"),
+ )'''
+ context['COMPILERS'] = '''{
+ "rest": ('.txt', '.rst'),
+ "markdown": ('.md', '.mdown', '.markdown', '.wp'),
+ "html": ('.html', '.htm')
+ }
+ '''
+ context['THEME'] = 'bootstrap3'
+
+ return context
+
+ def import_item(self, item, out_folder=None):
+ """Takes an item from the feed and creates a post file."""
+ if out_folder is None:
+ out_folder = 'posts'
+
+ # link is something like http://foo.com/2012/09/01/hello-world/
+ # So, take the path, utils.slugify it, and that's our slug
+ link = item.link
+ link_path = urlparse(link).path
+
+ title = item.title
+
+ # blogger supports empty titles, which Nikola doesn't
+ if not title:
+ LOGGER.warn("Empty title in post with URL {0}. Using NO_TITLE "
+ "as placeholder, please fix.".format(link))
+ title = "NO_TITLE"
+
+ if link_path.lower().endswith('.html'):
+ link_path = link_path[:-5]
+
+ slug = utils.slugify(link_path)
+
+ if not slug: # should never happen
+ LOGGER.error("Error converting post:", title)
+ return
+
+ description = ''
+ post_date = datetime.datetime.fromtimestamp(time.mktime(
+ item.published_parsed))
+
+ for candidate in item.content:
+ if candidate.type == 'text/html':
+ content = candidate.value
+ break
+ # FIXME: handle attachments
+
+ tags = []
+ for tag in item.tags:
+ if tag.scheme == 'http://www.blogger.com/atom/ns#':
+ tags.append(tag.term)
+
+ if item.get('app_draft'):
+ tags.append('draft')
+ is_draft = True
+ else:
+ is_draft = False
+
+ self.url_map[link] = self.context['SITE_URL'] + '/' + \
+ out_folder + '/' + slug + '.html'
+
+ if is_draft and self.exclude_drafts:
+ LOGGER.notice('Draft "{0}" will not be imported.'.format(title))
+ elif content.strip():
+ # If no content is found, no files are written.
+ content = self.transform_content(content)
+
+ self.write_metadata(os.path.join(self.output_folder, out_folder,
+ slug + '.meta'),
+ title, slug, post_date, description, tags)
+ self.write_content(
+ os.path.join(self.output_folder, out_folder, slug + '.html'),
+ content)
+ else:
+ LOGGER.warn('Not going to import "{0}" because it seems to contain'
+ ' no content.'.format(title))
+
+ def process_item(self, item):
+ post_type = item.tags[0].term
+
+ if post_type == 'http://schemas.google.com/blogger/2008/kind#post':
+ self.import_item(item, 'posts')
+ elif post_type == 'http://schemas.google.com/blogger/2008/kind#page':
+ self.import_item(item, 'stories')
+ elif post_type == ('http://schemas.google.com/blogger/2008/kind'
+ '#settings'):
+ # Ignore settings
+ pass
+ elif post_type == ('http://schemas.google.com/blogger/2008/kind'
+ '#template'):
+ # Ignore template
+ pass
+ elif post_type == ('http://schemas.google.com/blogger/2008/kind'
+ '#comment'):
+ # FIXME: not importing comments. Does blogger support "pages"?
+ pass
+ else:
+ LOGGER.warn("Unknown post_type:", post_type)
+
+ def import_posts(self, channel):
+ for item in channel.entries:
+ self.process_item(item)
diff --git a/nikola/plugins/command/import_feed.plugin b/nikola/plugins/command/import_feed.plugin
new file mode 100644
index 0000000..26e570a
--- /dev/null
+++ b/nikola/plugins/command/import_feed.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = import_feed
+Module = import_feed
+
+[Documentation]
+Author = Grzegorz Śliwiński
+Version = 0.1
+Website = http://www.fizyk.net.pl/
+Description = Import a blog posts from a RSS/Atom dump
+
diff --git a/nikola/plugins/command/import_feed.py b/nikola/plugins/command/import_feed.py
new file mode 100644
index 0000000..b25d9ec
--- /dev/null
+++ b/nikola/plugins/command/import_feed.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import unicode_literals, print_function
+import datetime
+import os
+import time
+
+try:
+ from urlparse import urlparse
+except ImportError:
+ from urllib.parse import urlparse # NOQA
+
+try:
+ import feedparser
+except ImportError:
+ feedparser = None # NOQA
+
+from nikola.plugin_categories import Command
+from nikola import utils
+from nikola.utils import req_missing
+from nikola.plugins.basic_import import ImportMixin
+
+LOGGER = utils.get_logger('import_feed', utils.STDERR_HANDLER)
+
+
+class CommandImportFeed(Command, ImportMixin):
+ """Import a feed dump."""
+
+ name = "import_feed"
+ needs_config = False
+ doc_usage = "[options] feed_file"
+ doc_purpose = "import a RSS/Atom dump"
+ cmd_options = ImportMixin.cmd_options
+
+ def _execute(self, options, args):
+ '''
+ Import Atom/RSS feed
+ '''
+ if feedparser is None:
+ req_missing(['feedparser'], 'import feeds')
+ return
+
+ if not args:
+ print(self.help())
+ return
+
+ options['filename'] = args[0]
+ self.feed_export_file = options['filename']
+ self.output_folder = options['output_folder']
+ self.import_into_existing_site = False
+ self.url_map = {}
+ channel = self.get_channel_from_file(self.feed_export_file)
+ self.context = self.populate_context(channel)
+ conf_template = self.generate_base_site()
+ self.context['REDIRECTIONS'] = self.configure_redirections(
+ self.url_map)
+
+ self.import_posts(channel)
+
+ self.write_configuration(self.get_configuration_output_path(
+ ), conf_template.render(**self.context))
+
+ @classmethod
+ def get_channel_from_file(cls, filename):
+ return feedparser.parse(filename)
+
+ @staticmethod
+ def populate_context(channel):
+ context = {}
+ context['DEFAULT_LANG'] = channel.feed.title_detail.language \
+ if channel.feed.title_detail.language else 'en'
+ context['BLOG_TITLE'] = channel.feed.title
+
+ context['BLOG_DESCRIPTION'] = channel.feed.get('subtitle', '')
+ context['SITE_URL'] = channel.feed.get('link', '').rstrip('/')
+ context['BLOG_EMAIL'] = channel.feed.author_detail.get('email', '') if 'author_detail' in channel.feed else ''
+ context['BLOG_AUTHOR'] = channel.feed.author_detail.get('name', '') if 'author_detail' in channel.feed else ''
+
+ context['POST_PAGES'] = '''(
+ ("posts/*.html", "posts", "post.tmpl", True),
+ ("stories/*.html", "stories", "story.tmpl", False),
+ )'''
+ context['COMPILERS'] = '''{
+ "rest": ('.txt', '.rst'),
+ "markdown": ('.md', '.mdown', '.markdown', '.wp'),
+ "html": ('.html', '.htm')
+ }
+ '''
+
+ return context
+
+ def import_posts(self, channel):
+ for item in channel.entries:
+ self.process_item(item)
+
+ def process_item(self, item):
+ self.import_item(item, 'posts')
+
+ def import_item(self, item, out_folder=None):
+ """Takes an item from the feed and creates a post file."""
+ if out_folder is None:
+ out_folder = 'posts'
+
+ # link is something like http://foo.com/2012/09/01/hello-world/
+ # So, take the path, utils.slugify it, and that's our slug
+ link = item.link
+ link_path = urlparse(link).path
+
+ title = item.title
+
+ # blogger supports empty titles, which Nikola doesn't
+ if not title:
+ LOGGER.warn("Empty title in post with URL {0}. Using NO_TITLE "
+ "as placeholder, please fix.".format(link))
+ title = "NO_TITLE"
+
+ if link_path.lower().endswith('.html'):
+ link_path = link_path[:-5]
+
+ slug = utils.slugify(link_path)
+
+ if not slug: # should never happen
+ LOGGER.error("Error converting post:", title)
+ return
+
+ description = ''
+ post_date = datetime.datetime.fromtimestamp(time.mktime(
+ item.published_parsed))
+ if item.get('content'):
+ for candidate in item.get('content', []):
+ content = candidate.value
+ break
+ # FIXME: handle attachments
+ elif item.get('summary'):
+ content = item.get('summary')
+
+ tags = []
+ for tag in item.get('tags', []):
+ tags.append(tag.term)
+
+ if item.get('app_draft'):
+ tags.append('draft')
+ is_draft = True
+ else:
+ is_draft = False
+
+ self.url_map[link] = self.context['SITE_URL'] + '/' + \
+ out_folder + '/' + slug + '.html'
+
+ if is_draft and self.exclude_drafts:
+ LOGGER.notice('Draft "{0}" will not be imported.'.format(title))
+ elif content.strip():
+ # If no content is found, no files are written.
+ content = self.transform_content(content)
+
+ self.write_metadata(os.path.join(self.output_folder, out_folder,
+ slug + '.meta'),
+ title, slug, post_date, description, tags)
+ self.write_content(
+ os.path.join(self.output_folder, out_folder, slug + '.html'),
+ content)
+ else:
+ LOGGER.warn('Not going to import "{0}" because it seems to contain'
+ ' no content.'.format(title))
+
+ @staticmethod
+ def write_metadata(filename, title, slug, post_date, description, tags):
+ ImportMixin.write_metadata(filename,
+ title,
+ slug,
+ post_date.strftime(r'%Y/%m/%d %H:%m:%S'),
+ description,
+ tags)
diff --git a/nikola/plugins/command/import_wordpress.plugin b/nikola/plugins/command/import_wordpress.plugin
new file mode 100644
index 0000000..fadc759
--- /dev/null
+++ b/nikola/plugins/command/import_wordpress.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = import_wordpress
+Module = import_wordpress
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.2
+Website = http://getnikola.com
+Description = Import a wordpress site from a XML dump (requires markdown).
+
diff --git a/nikola/plugins/command/import_wordpress.py b/nikola/plugins/command/import_wordpress.py
new file mode 100644
index 0000000..4f32198
--- /dev/null
+++ b/nikola/plugins/command/import_wordpress.py
@@ -0,0 +1,443 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import unicode_literals, print_function
+import os
+import re
+import sys
+from lxml import etree
+
+try:
+ from urlparse import urlparse
+ from urllib import unquote
+except ImportError:
+ from urllib.parse import urlparse, unquote # NOQA
+
+try:
+ import requests
+except ImportError:
+ requests = None # NOQA
+
+try:
+ import phpserialize
+except ImportError:
+ phpserialize = None # NOQA
+
+from nikola.plugin_categories import Command
+from nikola import utils
+from nikola.utils import req_missing
+from nikola.plugins.basic_import import ImportMixin, links
+
+LOGGER = utils.get_logger('import_wordpress', utils.STDERR_HANDLER)
+
+
+class CommandImportWordpress(Command, ImportMixin):
+ """Import a WordPress dump."""
+
+ name = "import_wordpress"
+ needs_config = False
+ doc_usage = "[options] wordpress_export_file"
+ doc_purpose = "import a WordPress dump"
+ cmd_options = ImportMixin.cmd_options + [
+ {
+ 'name': 'exclude_drafts',
+ 'long': 'no-drafts',
+ 'short': 'd',
+ 'default': False,
+ 'type': bool,
+ 'help': "Don't import drafts",
+ },
+ {
+ 'name': 'squash_newlines',
+ 'long': 'squash-newlines',
+ 'default': False,
+ 'type': bool,
+ 'help': "Shorten multiple newlines in a row to only two newlines",
+ },
+ {
+ 'name': 'no_downloads',
+ 'long': 'no-downloads',
+ 'default': False,
+ 'type': bool,
+ 'help': "Do not try to download files for the import",
+ },
+ ]
+
+ def _execute(self, options={}, args=[]):
+ """Import a WordPress blog from an export file into a Nikola site."""
+ if not args:
+ print(self.help())
+ return
+
+ options['filename'] = args.pop(0)
+
+ if args and ('output_folder' not in args or
+ options['output_folder'] == 'new_site'):
+ options['output_folder'] = args.pop(0)
+
+ if args:
+ LOGGER.warn('You specified additional arguments ({0}). Please consider '
+ 'putting these arguments before the filename if you '
+ 'are running into problems.'.format(args))
+
+ self.import_into_existing_site = False
+ self.url_map = {}
+ self.timezone = None
+
+ self.wordpress_export_file = options['filename']
+ self.squash_newlines = options.get('squash_newlines', False)
+ self.output_folder = options.get('output_folder', 'new_site')
+
+ self.exclude_drafts = options.get('exclude_drafts', False)
+ self.no_downloads = options.get('no_downloads', False)
+
+ if not self.no_downloads:
+ def show_info_about_mising_module(modulename):
+ LOGGER.error(
+ 'To use the "{commandname}" command, you have to install '
+ 'the "{package}" package or supply the "--no-downloads" '
+ 'option.'.format(
+ commandname=self.name,
+ package=modulename)
+ )
+
+ if requests is None and phpserialize is None:
+ req_missing(['requests', 'phpserialize'], 'import WordPress dumps without --no-downloads')
+ elif requests is None:
+ req_missing(['requests'], 'import WordPress dumps without --no-downloads')
+ elif phpserialize is None:
+ req_missing(['phpserialize'], 'import WordPress dumps without --no-downloads')
+
+ channel = self.get_channel_from_file(self.wordpress_export_file)
+ self.context = self.populate_context(channel)
+ conf_template = self.generate_base_site()
+
+ self.import_posts(channel)
+
+ self.context['REDIRECTIONS'] = self.configure_redirections(
+ self.url_map)
+ self.write_urlmap_csv(
+ os.path.join(self.output_folder, 'url_map.csv'), self.url_map)
+ rendered_template = conf_template.render(**self.context)
+ rendered_template = re.sub('# REDIRECTIONS = ', 'REDIRECTIONS = ',
+ rendered_template)
+ if self.timezone:
+ rendered_template = re.sub('# TIMEZONE = \'Europe/Zurich\'',
+ 'TIMEZONE = \'' + self.timezone + '\'',
+ rendered_template)
+ self.write_configuration(self.get_configuration_output_path(),
+ rendered_template)
+
+ @classmethod
+ def _glue_xml_lines(cls, xml):
+ new_xml = xml[0]
+ previous_line_ended_in_newline = new_xml.endswith(b'\n')
+ previous_line_was_indentet = False
+ for line in xml[1:]:
+ if (re.match(b'^[ \t]+', line) and previous_line_ended_in_newline):
+ new_xml = b''.join((new_xml, line))
+ previous_line_was_indentet = True
+ elif previous_line_was_indentet:
+ new_xml = b''.join((new_xml, line))
+ previous_line_was_indentet = False
+ else:
+ new_xml = b'\n'.join((new_xml, line))
+ previous_line_was_indentet = False
+
+ previous_line_ended_in_newline = line.endswith(b'\n')
+
+ return new_xml
+
+ @classmethod
+ def read_xml_file(cls, filename):
+ xml = []
+
+ with open(filename, 'rb') as fd:
+ for line in fd:
+ # These explode etree and are useless
+ if b' {1}".format(url, dst_path))
+ self.download_url_content_to_file(url, dst_path)
+ dst_url = '/'.join(dst_path.split(os.sep)[2:])
+ links[link] = '/' + dst_url
+ links[url] = '/' + dst_url
+
+ self.download_additional_image_sizes(
+ item,
+ wordpress_namespace,
+ os.path.dirname(url)
+ )
+
+ def download_additional_image_sizes(self, item, wordpress_namespace, source_path):
+ if phpserialize is None:
+ return
+
+ additional_metadata = item.findall('{{{0}}}postmeta'.format(wordpress_namespace))
+
+ if additional_metadata is None:
+ return
+
+ for element in additional_metadata:
+ meta_key = element.find('{{{0}}}meta_key'.format(wordpress_namespace))
+ if meta_key is not None and meta_key.text == '_wp_attachment_metadata':
+ meta_value = element.find('{{{0}}}meta_value'.format(wordpress_namespace))
+
+ if meta_value is None:
+ continue
+
+ # Someone from Wordpress thought it was a good idea
+ # serialize PHP objects into that metadata field. Given
+ # that the export should give you the power to insert
+ # your blogging into another site or system its not.
+ # Why don't they just use JSON?
+ if sys.version_info[0] == 2:
+ metadata = phpserialize.loads(meta_value.text)
+ size_key = 'sizes'
+ file_key = 'file'
+ else:
+ metadata = phpserialize.loads(meta_value.text.encode('UTF-8'))
+ size_key = b'sizes'
+ file_key = b'file'
+
+ if not size_key in metadata:
+ continue
+
+ for filename in [metadata[size_key][size][file_key] for size in metadata[size_key]]:
+ url = '/'.join([source_path, filename.decode('utf-8')])
+
+ path = urlparse(url).path
+ dst_path = os.path.join(*([self.output_folder, 'files']
+ + list(path.split('/'))))
+ dst_dir = os.path.dirname(dst_path)
+ utils.makedirs(dst_dir)
+ LOGGER.notice("Downloading {0} => {1}".format(url, dst_path))
+ self.download_url_content_to_file(url, dst_path)
+ dst_url = '/'.join(dst_path.split(os.sep)[2:])
+ links[url] = '/' + dst_url
+ links[url] = '/' + dst_url
+
+ @staticmethod
+ def transform_sourcecode(content):
+ new_content = re.sub('\[sourcecode language="([^"]+)"\]',
+ "\n~~~~~~~~~~~~{.\\1}\n", content)
+ new_content = new_content.replace('[/sourcecode]',
+ "\n~~~~~~~~~~~~\n")
+ return new_content
+
+ @staticmethod
+ def transform_caption(content):
+ new_caption = re.sub(r'\[/caption\]', '', content)
+ new_caption = re.sub(r'\[caption.*\]', '', new_caption)
+
+ return new_caption
+
+ def transform_multiple_newlines(self, content):
+ """Replaces multiple newlines with only two."""
+ if self.squash_newlines:
+ return re.sub(r'\n{3,}', r'\n\n', content)
+ else:
+ return content
+
+ def transform_content(self, content):
+ new_content = self.transform_sourcecode(content)
+ new_content = self.transform_caption(new_content)
+ new_content = self.transform_multiple_newlines(new_content)
+ return new_content
+
+ def import_item(self, item, wordpress_namespace, out_folder=None):
+ """Takes an item from the feed and creates a post file."""
+ if out_folder is None:
+ out_folder = 'posts'
+
+ title = get_text_tag(item, 'title', 'NO TITLE')
+ # link is something like http://foo.com/2012/09/01/hello-world/
+ # So, take the path, utils.slugify it, and that's our slug
+ link = get_text_tag(item, 'link', None)
+ path = unquote(urlparse(link).path)
+
+ # In python 2, path is a str. slug requires a unicode
+ # object. According to wikipedia, unquoted strings will
+ # usually be UTF8
+ if isinstance(path, utils.bytes_str):
+ path = path.decode('utf8')
+ slug = utils.slugify(path)
+ if not slug: # it happens if the post has no "nice" URL
+ slug = get_text_tag(
+ item, '{{{0}}}post_name'.format(wordpress_namespace), None)
+ if not slug: # it *may* happen
+ slug = get_text_tag(
+ item, '{{{0}}}post_id'.format(wordpress_namespace), None)
+ if not slug: # should never happen
+ LOGGER.error("Error converting post:", title)
+ return
+
+ description = get_text_tag(item, 'description', '')
+ post_date = get_text_tag(
+ item, '{{{0}}}post_date'.format(wordpress_namespace), None)
+ dt = utils.to_datetime(post_date)
+ if dt.tzinfo and self.timezone is None:
+ self.timezone = utils.get_tzname(dt)
+ status = get_text_tag(
+ item, '{{{0}}}status'.format(wordpress_namespace), 'publish')
+ content = get_text_tag(
+ item, '{http://purl.org/rss/1.0/modules/content/}encoded', '')
+
+ tags = []
+ if status == 'trash':
+ LOGGER.warn('Trashed post "{0}" will not be imported.'.format(title))
+ return
+ elif status != 'publish':
+ tags.append('draft')
+ is_draft = True
+ else:
+ is_draft = False
+
+ for tag in item.findall('category'):
+ text = tag.text
+ if text == 'Uncategorized':
+ continue
+ tags.append(text)
+
+ if is_draft and self.exclude_drafts:
+ LOGGER.notice('Draft "{0}" will not be imported.'.format(title))
+ elif content.strip():
+ # If no content is found, no files are written.
+ self.url_map[link] = self.context['SITE_URL'] + '/' + \
+ out_folder + '/' + slug + '.html'
+
+ content = self.transform_content(content)
+
+ self.write_metadata(os.path.join(self.output_folder, out_folder,
+ slug + '.meta'),
+ title, slug, post_date, description, tags)
+ self.write_content(
+ os.path.join(self.output_folder, out_folder, slug + '.wp'),
+ content)
+ else:
+ LOGGER.warn('Not going to import "{0}" because it seems to contain'
+ ' no content.'.format(title))
+
+ def process_item(self, item):
+ # The namespace usually is something like:
+ # http://wordpress.org/export/1.2/
+ wordpress_namespace = item.nsmap['wp']
+ post_type = get_text_tag(
+ item, '{{{0}}}post_type'.format(wordpress_namespace), 'post')
+
+ if post_type == 'attachment':
+ self.import_attachment(item, wordpress_namespace)
+ elif post_type == 'post':
+ self.import_item(item, wordpress_namespace, 'posts')
+ else:
+ self.import_item(item, wordpress_namespace, 'stories')
+
+ def import_posts(self, channel):
+ for item in channel.findall('item'):
+ self.process_item(item)
+
+
+def get_text_tag(tag, name, default):
+ if tag is None:
+ return default
+ t = tag.find(name)
+ if t is not None:
+ return t.text
+ else:
+ return default
diff --git a/nikola/plugins/command/init.plugin b/nikola/plugins/command/init.plugin
new file mode 100644
index 0000000..a539f51
--- /dev/null
+++ b/nikola/plugins/command/init.plugin
@@ -0,0 +1,9 @@
+[Core]
+Name = init
+Module = init
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.2
+Website = http://getnikola.com
+Description = Create a new site.
diff --git a/nikola/plugins/command/init.py b/nikola/plugins/command/init.py
new file mode 100644
index 0000000..1873ec4
--- /dev/null
+++ b/nikola/plugins/command/init.py
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+import os
+import shutil
+import codecs
+
+from mako.template import Template
+
+import nikola
+from nikola.plugin_categories import Command
+from nikola.utils import get_logger, makedirs, STDERR_HANDLER
+from nikola.winutils import fix_git_symlinked
+
+LOGGER = get_logger('init', STDERR_HANDLER)
+
+
+class CommandInit(Command):
+ """Create a new site."""
+
+ name = "init"
+
+ doc_usage = "[--demo] folder"
+ needs_config = False
+ doc_purpose = "create a Nikola site in the specified folder"
+ cmd_options = [
+ {
+ 'name': 'demo',
+ 'long': 'demo',
+ 'default': False,
+ 'type': bool,
+ 'help': "Create a site filled with example data.",
+ }
+ ]
+
+ SAMPLE_CONF = {
+ 'BLOG_AUTHOR': "Your Name",
+ 'BLOG_TITLE': "Demo Site",
+ 'SITE_URL': "http://getnikola.com/",
+ 'BLOG_EMAIL': "joe@demo.site",
+ 'BLOG_DESCRIPTION': "This is a demo site for Nikola.",
+ 'DEFAULT_LANG': "en",
+ 'THEME': 'bootstrap3',
+
+ 'POSTS': """(
+ ("posts/*.rst", "posts", "post.tmpl"),
+ ("posts/*.txt", "posts", "post.tmpl"),
+)""",
+ 'PAGES': """(
+ ("stories/*.rst", "stories", "story.tmpl"),
+ ("stories/*.txt", "stories", "story.tmpl"),
+)""",
+ 'COMPILERS': """{
+ "rest": ('.rst', '.txt'),
+ "markdown": ('.md', '.mdown', '.markdown'),
+ "textile": ('.textile',),
+ "txt2tags": ('.t2t',),
+ "bbcode": ('.bb',),
+ "wiki": ('.wiki',),
+ "ipynb": ('.ipynb',),
+ "html": ('.html', '.htm'),
+ # Pandoc detects the input from the source filename
+ # but is disabled by default as it would conflict
+ # with many of the others.
+ # "pandoc": ('.rst', '.md', '.txt'),
+}""",
+ 'REDIRECTIONS': '[]',
+ }
+
+ @classmethod
+ def copy_sample_site(cls, target):
+ lib_path = cls.get_path_to_nikola_modules()
+ src = os.path.join(lib_path, 'data', 'samplesite')
+ shutil.copytree(src, target)
+ fix_git_symlinked(src, target)
+
+ @classmethod
+ def create_configuration(cls, target):
+ lib_path = cls.get_path_to_nikola_modules()
+ template_path = os.path.join(lib_path, 'conf.py.in')
+ conf_template = Template(filename=template_path)
+ conf_path = os.path.join(target, 'conf.py')
+ with codecs.open(conf_path, 'w+', 'utf8') as fd:
+ fd.write(conf_template.render(**cls.SAMPLE_CONF))
+
+ @classmethod
+ def create_empty_site(cls, target):
+ for folder in ('files', 'galleries', 'listings', 'posts', 'stories'):
+ makedirs(os.path.join(target, folder))
+
+ @staticmethod
+ def get_path_to_nikola_modules():
+ return os.path.dirname(nikola.__file__)
+
+ def _execute(self, options={}, args=None):
+ """Create a new site."""
+ if not args:
+ print("Usage: nikola init folder [options]")
+ return False
+ target = args[0]
+ if target is None:
+ print(self.usage)
+ else:
+ if not options or not options.get('demo'):
+ self.create_empty_site(target)
+ LOGGER.notice('Created empty site at {0}.'.format(target))
+ else:
+ self.copy_sample_site(target)
+ LOGGER.notice("A new site with example data has been created at "
+ "{0}.".format(target))
+ LOGGER.notice("See README.txt in that folder for more information.")
+
+ self.create_configuration(target)
diff --git a/nikola/plugins/command/install_plugin.plugin b/nikola/plugins/command/install_plugin.plugin
new file mode 100644
index 0000000..3dbabd8
--- /dev/null
+++ b/nikola/plugins/command/install_plugin.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = install_plugin
+Module = install_plugin
+
+[Documentation]
+Author = Roberto Alsina and Chris Warrick
+Version = 0.1
+Website = http://getnikola.com
+Description = Install a plugin into the current site.
+
diff --git a/nikola/plugins/command/install_plugin.py b/nikola/plugins/command/install_plugin.py
new file mode 100644
index 0000000..fdbd0b7
--- /dev/null
+++ b/nikola/plugins/command/install_plugin.py
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+import codecs
+import os
+import json
+import shutil
+import subprocess
+from io import BytesIO
+
+import pygments
+from pygments.lexers import PythonLexer
+from pygments.formatters import TerminalFormatter
+
+try:
+ import requests
+except ImportError:
+ requests = None # NOQA
+
+from nikola.plugin_categories import Command
+from nikola import utils
+
+LOGGER = utils.get_logger('install_plugin', utils.STDERR_HANDLER)
+
+
+# Stolen from textwrap in Python 3.3.2.
+def indent(text, prefix, predicate=None): # NOQA
+ """Adds 'prefix' to the beginning of selected lines in 'text'.
+
+ If 'predicate' is provided, 'prefix' will only be added to the lines
+ where 'predicate(line)' is True. If 'predicate' is not provided,
+ it will default to adding 'prefix' to all non-empty lines that do not
+ consist solely of whitespace characters.
+ """
+ if predicate is None:
+ def predicate(line):
+ return line.strip()
+
+ def prefixed_lines():
+ for line in text.splitlines(True):
+ yield (prefix + line if predicate(line) else line)
+ return ''.join(prefixed_lines())
+
+
+class CommandInstallPlugin(Command):
+ """Install a plugin."""
+
+ name = "install_plugin"
+ doc_usage = "[[-u] plugin_name] | [[-u] -l]"
+ doc_purpose = "install plugin into current site"
+ output_dir = 'plugins'
+ cmd_options = [
+ {
+ 'name': 'list',
+ 'short': 'l',
+ 'long': 'list',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Show list of available plugins.'
+ },
+ {
+ 'name': 'url',
+ 'short': 'u',
+ 'long': 'url',
+ 'type': str,
+ 'help': "URL for the plugin repository (default: "
+ "http://plugins.getnikola.com/v6/plugins.json)",
+ 'default': 'http://plugins.getnikola.com/v6/plugins.json'
+ },
+ ]
+
+ def _execute(self, options, args):
+ """Install plugin into current site."""
+ if requests is None:
+ utils.req_missing(['requests'], 'install plugins')
+
+ listing = options['list']
+ url = options['url']
+ if args:
+ name = args[0]
+ else:
+ name = None
+
+ if name is None and not listing:
+ LOGGER.error("This command needs either a plugin name or the -l option.")
+ return False
+ data = requests.get(url).text
+ data = json.loads(data)
+ if listing:
+ print("Plugins:")
+ print("--------")
+ for plugin in sorted(data.keys()):
+ print(plugin)
+ return True
+ else:
+ self.do_install(name, data)
+
+ def do_install(self, name, data):
+ if name in data:
+ utils.makedirs(self.output_dir)
+ LOGGER.notice('Downloading: ' + data[name])
+ zip_file = BytesIO()
+ zip_file.write(requests.get(data[name]).content)
+ LOGGER.notice('Extracting: {0} into plugins'.format(name))
+ utils.extract_all(zip_file, 'plugins')
+ dest_path = os.path.join('plugins', name)
+ else:
+ try:
+ plugin_path = utils.get_plugin_path(name)
+ except:
+ LOGGER.error("Can't find plugin " + name)
+ return False
+
+ utils.makedirs(self.output_dir)
+ dest_path = os.path.join(self.output_dir, name)
+ if os.path.exists(dest_path):
+ LOGGER.error("{0} is already installed".format(name))
+ return False
+
+ LOGGER.notice('Copying {0} into plugins'.format(plugin_path))
+ shutil.copytree(plugin_path, dest_path)
+
+ reqpath = os.path.join(dest_path, 'requirements.txt')
+ print(reqpath)
+ if os.path.exists(reqpath):
+ LOGGER.notice('This plugin has Python dependencies.')
+ LOGGER.notice('Installing dependencies with pip...')
+ try:
+ subprocess.check_call(('pip', 'install', '-r', reqpath))
+ except subprocess.CalledProcessError:
+ LOGGER.error('Could not install the dependencies.')
+ print('Contents of the requirements.txt file:\n')
+ with codecs.open(reqpath, 'rb', 'utf-8') as fh:
+ print(indent(fh.read(), 4 * ' '))
+ print('You have to install those yourself or through a '
+ 'package manager.')
+ else:
+ LOGGER.notice('Dependency installation succeeded.')
+ reqnpypath = os.path.join(dest_path, 'requirements-nonpy.txt')
+ if os.path.exists(reqnpypath):
+ LOGGER.notice('This plugin has third-party '
+ 'dependencies you need to install '
+ 'manually.')
+ print('Contents of the requirements-nonpy.txt file:\n')
+ with codecs.open(reqnpypath, 'rb', 'utf-8') as fh:
+ for l in fh.readlines():
+ i, j = l.split('::')
+ print(indent(i.strip(), 4 * ' '))
+ print(indent(j.strip(), 8 * ' '))
+ print()
+
+ print('You have to install those yourself or through a package '
+ 'manager.')
+ confpypath = os.path.join(dest_path, 'conf.py.sample')
+ if os.path.exists(confpypath):
+ LOGGER.notice('This plugin has a sample config file.')
+ print('Contents of the conf.py.sample file:\n')
+ with codecs.open(confpypath, 'rb', 'utf-8') as fh:
+ print(indent(pygments.highlight(
+ fh.read(), PythonLexer(), TerminalFormatter()), 4 * ' '))
+ return True
diff --git a/nikola/plugins/command/install_theme.plugin b/nikola/plugins/command/install_theme.plugin
new file mode 100644
index 0000000..84b2623
--- /dev/null
+++ b/nikola/plugins/command/install_theme.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = install_theme
+Module = install_theme
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Install a theme into the current site.
+
diff --git a/nikola/plugins/command/install_theme.py b/nikola/plugins/command/install_theme.py
new file mode 100644
index 0000000..a9d835a
--- /dev/null
+++ b/nikola/plugins/command/install_theme.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+import os
+import json
+import shutil
+import codecs
+from io import BytesIO
+
+import pygments
+from pygments.lexers import PythonLexer
+from pygments.formatters import TerminalFormatter
+
+try:
+ import requests
+except ImportError:
+ requests = None # NOQA
+
+from nikola.plugin_categories import Command
+from nikola import utils
+
+LOGGER = utils.get_logger('install_theme', utils.STDERR_HANDLER)
+
+
+# Stolen from textwrap in Python 3.3.2.
+def indent(text, prefix, predicate=None): # NOQA
+ """Adds 'prefix' to the beginning of selected lines in 'text'.
+
+ If 'predicate' is provided, 'prefix' will only be added to the lines
+ where 'predicate(line)' is True. If 'predicate' is not provided,
+ it will default to adding 'prefix' to all non-empty lines that do not
+ consist solely of whitespace characters.
+ """
+ if predicate is None:
+ def predicate(line):
+ return line.strip()
+
+ def prefixed_lines():
+ for line in text.splitlines(True):
+ yield (prefix + line if predicate(line) else line)
+ return ''.join(prefixed_lines())
+
+
+class CommandInstallTheme(Command):
+ """Install a theme."""
+
+ name = "install_theme"
+ doc_usage = "[[-u] theme_name] | [[-u] -l]"
+ doc_purpose = "install theme into current site"
+ output_dir = 'themes'
+ cmd_options = [
+ {
+ 'name': 'list',
+ 'short': 'l',
+ 'long': 'list',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Show list of available themes.'
+ },
+ {
+ 'name': 'url',
+ 'short': 'u',
+ 'long': 'url',
+ 'type': str,
+ 'help': "URL for the theme repository (default: "
+ "http://themes.getnikola.com/v6/themes.json)",
+ 'default': 'http://themes.getnikola.com/v6/themes.json'
+ },
+ ]
+
+ def _execute(self, options, args):
+ """Install theme into current site."""
+ if requests is None:
+ utils.req_missing(['requests'], 'install themes')
+
+ listing = options['list']
+ url = options['url']
+ if args:
+ name = args[0]
+ else:
+ name = None
+
+ if name is None and not listing:
+ LOGGER.error("This command needs either a theme name or the -l option.")
+ return False
+ data = requests.get(url).text
+ data = json.loads(data)
+ if listing:
+ print("Themes:")
+ print("-------")
+ for theme in sorted(data.keys()):
+ print(theme)
+ return True
+ else:
+ self.do_install(name, data)
+ # See if the theme's parent is available. If not, install it
+ while True:
+ parent_name = utils.get_parent_theme_name(name)
+ if parent_name is None:
+ break
+ try:
+ utils.get_theme_path(parent_name)
+ break
+ except: # Not available
+ self.do_install(parent_name, data)
+ name = parent_name
+
+ def do_install(self, name, data):
+ if name in data:
+ utils.makedirs(self.output_dir)
+ LOGGER.notice('Downloading: ' + data[name])
+ zip_file = BytesIO()
+ zip_file.write(requests.get(data[name]).content)
+ LOGGER.notice('Extracting: {0} into themes'.format(name))
+ utils.extract_all(zip_file)
+ dest_path = os.path.join('themes', name)
+ else:
+ try:
+ theme_path = utils.get_theme_path(name)
+ except:
+ LOGGER.error("Can't find theme " + name)
+ return False
+
+ utils.makedirs(self.output_dir)
+ dest_path = os.path.join(self.output_dir, name)
+ if os.path.exists(dest_path):
+ LOGGER.error("{0} is already installed".format(name))
+ return False
+
+ LOGGER.notice('Copying {0} into themes'.format(theme_path))
+ shutil.copytree(theme_path, dest_path)
+ confpypath = os.path.join(dest_path, 'conf.py.sample')
+ if os.path.exists(confpypath):
+ LOGGER.notice('This plugin has a sample config file.')
+ print('Contents of the conf.py.sample file:\n')
+ with codecs.open(confpypath, 'rb', 'utf-8') as fh:
+ print(indent(pygments.highlight(
+ fh.read(), PythonLexer(), TerminalFormatter()), 4 * ' '))
+ return True
diff --git a/nikola/plugins/command/mincss.plugin b/nikola/plugins/command/mincss.plugin
new file mode 100644
index 0000000..d394d06
--- /dev/null
+++ b/nikola/plugins/command/mincss.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = mincss
+Module = mincss
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Apply mincss to the generated site
+
diff --git a/nikola/plugins/command/mincss.py b/nikola/plugins/command/mincss.py
new file mode 100644
index 0000000..5c9a7cb
--- /dev/null
+++ b/nikola/plugins/command/mincss.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function, unicode_literals
+import os
+import sys
+
+try:
+ from mincss.processor import Processor
+except ImportError:
+ Processor = None
+
+from nikola.plugin_categories import Command
+from nikola.utils import req_missing, get_logger, STDERR_HANDLER
+
+
+class CommandMincss(Command):
+ """Check the generated site."""
+ name = "mincss"
+
+ doc_usage = ""
+ doc_purpose = "apply mincss to the generated site"
+
+ logger = get_logger('mincss', STDERR_HANDLER)
+
+ def _execute(self, options, args):
+ """Apply mincss the generated site."""
+ output_folder = self.site.config['OUTPUT_FOLDER']
+ if Processor is None:
+ req_missing(['mincss'], 'use the "mincss" command')
+ return
+
+ p = Processor(preserve_remote_urls=False)
+ urls = []
+ css_files = {}
+ for root, dirs, files in os.walk(output_folder):
+ for f in files:
+ url = os.path.join(root, f)
+ if url.endswith('.css'):
+ fname = os.path.basename(url)
+ if fname in css_files:
+ self.logger.error("You have two CSS files with the same name and that confuses me.")
+ sys.exit(1)
+ css_files[fname] = url
+ if not f.endswith('.html'):
+ continue
+ urls.append(url)
+ p.process(*urls)
+ for inline in p.links:
+ fname = os.path.basename(inline.href)
+ with open(css_files[fname], 'wb+') as outf:
+ outf.write(inline.after)
diff --git a/nikola/plugins/command/new_post.plugin b/nikola/plugins/command/new_post.plugin
new file mode 100644
index 0000000..ec35c35
--- /dev/null
+++ b/nikola/plugins/command/new_post.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = new_post
+Module = new_post
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Create a new post.
+
diff --git a/nikola/plugins/command/new_post.py b/nikola/plugins/command/new_post.py
new file mode 100644
index 0000000..ea0f3de
--- /dev/null
+++ b/nikola/plugins/command/new_post.py
@@ -0,0 +1,291 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import unicode_literals, print_function
+import codecs
+import datetime
+import os
+import sys
+
+from blinker import signal
+
+from nikola.plugin_categories import Command
+from nikola import utils
+
+LOGGER = utils.get_logger('new_post', utils.STDERR_HANDLER)
+
+
+def filter_post_pages(compiler, is_post, compilers, post_pages):
+ """Given a compiler ("markdown", "rest"), and whether it's meant for
+ a post or a page, and compilers, return the correct entry from
+ post_pages."""
+
+ # First throw away all the post_pages with the wrong is_post
+ filtered = [entry for entry in post_pages if entry[3] == is_post]
+
+ # These are the extensions supported by the required format
+ extensions = compilers[compiler]
+
+ # Throw away the post_pages with the wrong extensions
+ filtered = [entry for entry in filtered if any([ext in entry[0] for ext in
+ extensions])]
+
+ if not filtered:
+ type_name = "post" if is_post else "page"
+ raise Exception("Can't find a way, using your configuration, to create "
+ "a {0} in format {1}. You may want to tweak "
+ "COMPILERS or POSTS/PAGES in conf.py".format(
+ type_name, compiler))
+ return filtered[0]
+
+
+def get_default_compiler(is_post, compilers, post_pages):
+ """Given compilers and post_pages, return a reasonable
+ default compiler for this kind of post/page.
+ """
+
+ # First throw away all the post_pages with the wrong is_post
+ filtered = [entry for entry in post_pages if entry[3] == is_post]
+
+ # Get extensions in filtered post_pages until one matches a compiler
+ for entry in filtered:
+ extension = os.path.splitext(entry[0])[-1]
+ for compiler, extensions in compilers.items():
+ if extension in extensions:
+ return compiler
+ # No idea, back to default behaviour
+ return 'rest'
+
+
+def get_date(schedule=False, rule=None, last_date=None, force_today=False):
+ """Returns a date stamp, given a recurrence rule.
+
+ schedule - bool:
+ whether to use the recurrence rule or not
+
+ rule - str:
+ an iCal RRULE string that specifies the rule for scheduling posts
+
+ last_date - datetime:
+ timestamp of the last post
+
+ force_today - bool:
+ tries to schedule a post to today, if possible, even if the scheduled
+ time has already passed in the day.
+ """
+
+ date = now = datetime.datetime.now()
+ if schedule:
+ try:
+ from dateutil import rrule
+ except ImportError:
+ LOGGER.error('To use the --schedule switch of new_post, '
+ 'you have to install the "dateutil" package.')
+ rrule = None
+ if schedule and rrule and rule:
+ if last_date and last_date.tzinfo:
+ # strip tzinfo for comparisons
+ last_date = last_date.replace(tzinfo=None)
+ try:
+ rule_ = rrule.rrulestr(rule, dtstart=last_date)
+ except Exception:
+ LOGGER.error('Unable to parse rule string, using current time.')
+ else:
+ # Try to post today, instead of tomorrow, if no other post today.
+ if force_today:
+ now = now.replace(hour=0, minute=0, second=0, microsecond=0)
+ date = rule_.after(max(now, last_date or now), last_date is None)
+ return date.strftime('%Y/%m/%d %H:%M:%S')
+
+
+class CommandNewPost(Command):
+ """Create a new post."""
+
+ name = "new_post"
+ doc_usage = "[options] [path]"
+ doc_purpose = "create a new blog post or site page"
+ cmd_options = [
+ {
+ 'name': 'is_page',
+ 'short': 'p',
+ 'long': 'page',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Create a page instead of a blog post.'
+ },
+ {
+ 'name': 'title',
+ 'short': 't',
+ 'long': 'title',
+ 'type': str,
+ 'default': '',
+ 'help': 'Title for the page/post.'
+ },
+ {
+ 'name': 'tags',
+ 'long': 'tags',
+ 'type': str,
+ 'default': '',
+ 'help': 'Comma-separated tags for the page/post.'
+ },
+ {
+ 'name': 'onefile',
+ 'short': '1',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Create post with embedded metadata (single file format)'
+ },
+ {
+ 'name': 'twofile',
+ 'short': '2',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Create post with separate metadata (two file format)'
+ },
+ {
+ 'name': 'post_format',
+ 'short': 'f',
+ 'long': 'format',
+ 'type': str,
+ 'default': '',
+ 'help': 'Markup format for post, one of rest, markdown, wiki, '
+ 'bbcode, html, textile, txt2tags',
+ },
+ {
+ 'name': 'schedule',
+ 'short': 's',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Schedule post based on recurrence rule'
+ },
+
+ ]
+
+ def _execute(self, options, args):
+ """Create a new post or page."""
+ compiler_names = [p.name for p in
+ self.site.plugin_manager.getPluginsOfCategory(
+ "PageCompiler")]
+
+ if len(args) > 1:
+ print(self.help())
+ return False
+ elif args:
+ path = args[0]
+ else:
+ path = None
+
+ is_page = options.get('is_page', False)
+ is_post = not is_page
+ title = options['title'] or None
+ tags = options['tags']
+ onefile = options['onefile']
+ twofile = options['twofile']
+
+ if twofile:
+ onefile = False
+ if not onefile and not twofile:
+ onefile = self.site.config.get('ONE_FILE_POSTS', True)
+
+ post_format = options['post_format']
+
+ if not post_format: # Issue #400
+ post_format = get_default_compiler(
+ is_post,
+ self.site.config['COMPILERS'],
+ self.site.config['post_pages'])
+
+ if post_format not in compiler_names:
+ LOGGER.error("Unknown post format " + post_format)
+ return
+ compiler_plugin = self.site.plugin_manager.getPluginByName(
+ post_format, "PageCompiler").plugin_object
+
+ # Guess where we should put this
+ entry = filter_post_pages(post_format, is_post,
+ self.site.config['COMPILERS'],
+ self.site.config['post_pages'])
+
+ print("Creating New Post")
+ print("-----------------\n")
+ if title is None:
+ print("Enter title: ", end='')
+ # WHY, PYTHON3???? WHY?
+ sys.stdout.flush()
+ title = sys.stdin.readline()
+ else:
+ print("Title:", title)
+ if isinstance(title, utils.bytes_str):
+ title = title.decode(sys.stdin.encoding)
+ title = title.strip()
+ if not path:
+ slug = utils.slugify(title)
+ else:
+ if isinstance(path, utils.bytes_str):
+ path = path.decode(sys.stdin.encoding)
+ slug = utils.slugify(os.path.splitext(os.path.basename(path))[0])
+ # Calculate the date to use for the post
+ schedule = options['schedule'] or self.site.config['SCHEDULE_ALL']
+ rule = self.site.config['SCHEDULE_RULE']
+ force_today = self.site.config['SCHEDULE_FORCE_TODAY']
+ self.site.scan_posts()
+ timeline = self.site.timeline
+ last_date = None if not timeline else timeline[0].date
+ date = get_date(schedule, rule, last_date, force_today)
+ data = [title, slug, date, tags]
+ output_path = os.path.dirname(entry[0])
+ meta_path = os.path.join(output_path, slug + ".meta")
+ pattern = os.path.basename(entry[0])
+ suffix = pattern[1:]
+ if not path:
+ txt_path = os.path.join(output_path, slug + suffix)
+ else:
+ txt_path = path
+
+ if (not onefile and os.path.isfile(meta_path)) or \
+ os.path.isfile(txt_path):
+ LOGGER.error("The title already exists!")
+ exit()
+
+ d_name = os.path.dirname(txt_path)
+ utils.makedirs(d_name)
+ metadata = self.site.config['ADDITIONAL_METADATA']
+ compiler_plugin.create_post(
+ txt_path, onefile, title=title,
+ slug=slug, date=date, tags=tags, **metadata)
+
+ event = dict(path=txt_path)
+
+ if not onefile: # write metadata file
+ with codecs.open(meta_path, "wb+", "utf8") as fd:
+ fd.write('\n'.join(data))
+ with codecs.open(txt_path, "wb+", "utf8") as fd:
+ fd.write("Write your post here.")
+ LOGGER.notice("Your post's metadata is at: {0}".format(meta_path))
+ event['meta_path'] = meta_path
+ LOGGER.notice("Your post's text is at: {0}".format(txt_path))
+
+ signal('new_post').send(self, **event)
diff --git a/nikola/plugins/command/planetoid.plugin b/nikola/plugins/command/planetoid.plugin
new file mode 100644
index 0000000..e767f31
--- /dev/null
+++ b/nikola/plugins/command/planetoid.plugin
@@ -0,0 +1,9 @@
+[Core]
+Name = planetoid
+Module = planetoid
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Maintain a planet-like site
diff --git a/nikola/plugins/command/planetoid/__init__.py b/nikola/plugins/command/planetoid/__init__.py
new file mode 100644
index 0000000..369862b
--- /dev/null
+++ b/nikola/plugins/command/planetoid/__init__.py
@@ -0,0 +1,289 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function, unicode_literals
+import codecs
+import datetime
+import hashlib
+from optparse import OptionParser
+import os
+import sys
+
+from doit.tools import timeout
+from nikola.plugin_categories import Command, Task
+from nikola.utils import config_changed, req_missing, get_logger, STDERR_HANDLER
+
+LOGGER = get_logger('planetoid', STDERR_HANDLER)
+
+try:
+ import feedparser
+except ImportError:
+ feedparser = None # NOQA
+
+try:
+ import peewee
+except ImportError:
+ peewee = None
+
+
+if peewee is not None:
+ class Feed(peewee.Model):
+ name = peewee.CharField()
+ url = peewee.CharField(max_length=200)
+ last_status = peewee.CharField(null=True)
+ etag = peewee.CharField(max_length=200)
+ last_modified = peewee.DateTimeField()
+
+ class Entry(peewee.Model):
+ date = peewee.DateTimeField()
+ feed = peewee.ForeignKeyField(Feed)
+ content = peewee.TextField(max_length=20000)
+ link = peewee.CharField(max_length=200)
+ title = peewee.CharField(max_length=200)
+ guid = peewee.CharField(max_length=200)
+
+
+class Planetoid(Command, Task):
+ """Maintain a planet-like thing."""
+ name = "planetoid"
+
+ def init_db(self):
+ # setup database
+ Feed.create_table(fail_silently=True)
+ Entry.create_table(fail_silently=True)
+
+ def gen_tasks(self):
+ if peewee is None or sys.version_info[0] == 3:
+ if sys.version_info[0] == 3:
+ message = 'Peewee, a requirement of the "planetoid" command, is currently incompatible with Python 3.'
+ else:
+ req_missing('peewee', 'use the "planetoid" command')
+ message = ''
+ yield {
+ 'basename': self.name,
+ 'name': '',
+ 'verbosity': 2,
+ 'actions': ['echo "%s"' % message]
+ }
+ else:
+ self.init_db()
+ self.load_feeds()
+ for task in self.task_update_feeds():
+ yield task
+ for task in self.task_generate_posts():
+ yield task
+ yield {
+ 'basename': self.name,
+ 'name': '',
+ 'actions': [],
+ 'file_dep': ['feeds'],
+ 'task_dep': [
+ self.name + "_fetch_feed",
+ self.name + "_generate_posts",
+ ]
+ }
+
+ def run(self, *args):
+ parser = OptionParser(usage="nikola %s [options]" % self.name)
+ (options, args) = parser.parse_args(list(args))
+
+ def load_feeds(self):
+ "Read the feeds file, add it to the database."
+ feeds = []
+ feed = name = None
+ for line in codecs.open('feeds', 'r', 'utf-8'):
+ line = line.strip()
+ if line.startswith("#"):
+ continue
+ elif line.startswith('http'):
+ feed = line
+ elif line:
+ name = line
+ if feed and name:
+ feeds.append([feed, name])
+ feed = name = None
+
+ def add_feed(name, url):
+ f = Feed.create(
+ name=name,
+ url=url,
+ etag='foo',
+ last_modified=datetime.datetime(1970, 1, 1),
+ )
+ f.save()
+
+ def update_feed_url(feed, url):
+ feed.url = url
+ feed.save()
+
+ for feed, name in feeds:
+ f = Feed.select().where(Feed.name == name)
+ if not list(f):
+ add_feed(name, feed)
+ elif list(f)[0].url != feed:
+ update_feed_url(list(f)[0], feed)
+
+ def task_update_feeds(self):
+ """Download feed contents, add entries to the database."""
+ def update_feed(feed):
+ modified = feed.last_modified.timetuple()
+ etag = feed.etag
+ try:
+ parsed = feedparser.parse(
+ feed.url,
+ etag=etag,
+ modified=modified
+ )
+ feed.last_status = str(parsed.status)
+ except: # Probably a timeout
+ # TODO: log failure
+ return
+ if parsed.feed.get('title'):
+ LOGGER.notice(parsed.feed.title)
+ else:
+ LOGGER.notice(feed.url)
+ feed.etag = parsed.get('etag', 'foo')
+ modified = tuple(parsed.get('date_parsed', (1970, 1, 1)))[:6]
+ LOGGER.notice("==========>", modified)
+ modified = datetime.datetime(*modified)
+ feed.last_modified = modified
+ feed.save()
+ # No point in adding items from missinfg feeds
+ if parsed.status > 400:
+ # TODO log failure
+ return
+ for entry_data in parsed.entries:
+ LOGGER.notice("=========================================")
+ date = entry_data.get('published_parsed', None)
+ if date is None:
+ date = entry_data.get('updated_parsed', None)
+ if date is None:
+ LOGGER.error("Can't parse date from:\n", entry_data)
+ return False
+ LOGGER.notice("DATE:===>", date)
+ date = datetime.datetime(*(date[:6]))
+ title = "%s: %s" % (feed.name, entry_data.get('title', 'Sin título'))
+ content = entry_data.get('content', None)
+ if content:
+ content = content[0].value
+ if not content:
+ content = entry_data.get('description', None)
+ if not content:
+ content = entry_data.get('summary', 'Sin contenido')
+ guid = str(entry_data.get('guid', entry_data.link))
+ link = entry_data.link
+ LOGGER.notice(repr([date, title]))
+ e = list(Entry.select().where(Entry.guid == guid))
+ LOGGER.notice(
+ repr(dict(
+ date=date,
+ title=title,
+ content=content,
+ guid=guid,
+ feed=feed,
+ link=link,
+ ))
+ )
+ if not e:
+ entry = Entry.create(
+ date=date,
+ title=title,
+ content=content,
+ guid=guid,
+ feed=feed,
+ link=link,
+ )
+ else:
+ entry = e[0]
+ entry.date = date
+ entry.title = title
+ entry.content = content
+ entry.link = link
+ entry.save()
+ flag = False
+ for feed in Feed.select():
+ flag = True
+ task = {
+ 'basename': self.name + "_fetch_feed",
+ 'name': str(feed.url),
+ 'actions': [(update_feed, (feed, ))],
+ 'uptodate': [timeout(datetime.timedelta(minutes=
+ self.site.config.get('PLANETOID_REFRESH', 60)))],
+ }
+ yield task
+ if not flag:
+ yield {
+ 'basename': self.name + "_fetch_feed",
+ 'name': '',
+ 'actions': [],
+ }
+
+ def task_generate_posts(self):
+ """Generate post files for the blog entries."""
+ def gen_id(entry):
+ h = hashlib.md5()
+ h.update(entry.feed.name.encode('utf8'))
+ h.update(entry.guid)
+ return h.hexdigest()
+
+ def generate_post(entry):
+ unique_id = gen_id(entry)
+ meta_path = os.path.join('posts', unique_id + '.meta')
+ post_path = os.path.join('posts', unique_id + '.txt')
+ with codecs.open(meta_path, 'wb+', 'utf8') as fd:
+ fd.write('%s\n' % entry.title.replace('\n', ' '))
+ fd.write('%s\n' % unique_id)
+ fd.write('%s\n' % entry.date.strftime('%Y/%m/%d %H:%M'))
+ fd.write('\n')
+ fd.write('%s\n' % entry.link)
+ with codecs.open(post_path, 'wb+', 'utf8') as fd:
+ fd.write('.. raw:: html\n\n')
+ content = entry.content
+ if not content:
+ content = 'Sin contenido'
+ for line in content.splitlines():
+ fd.write(' %s\n' % line)
+
+ if not os.path.isdir('posts'):
+ os.mkdir('posts')
+ flag = False
+ for entry in Entry.select().order_by(Entry.date.desc()):
+ flag = True
+ entry_id = gen_id(entry)
+ yield {
+ 'basename': self.name + "_generate_posts",
+ 'targets': [os.path.join('posts', entry_id + '.meta'), os.path.join('posts', entry_id + '.txt')],
+ 'name': entry_id,
+ 'actions': [(generate_post, (entry,))],
+ 'uptodate': [config_changed({1: entry})],
+ 'task_dep': [self.name + "_fetch_feed"],
+ }
+ if not flag:
+ yield {
+ 'basename': self.name + "_generate_posts",
+ 'name': '',
+ 'actions': [],
+ }
diff --git a/nikola/plugins/command/serve.plugin b/nikola/plugins/command/serve.plugin
new file mode 100644
index 0000000..e663cc6
--- /dev/null
+++ b/nikola/plugins/command/serve.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = serve
+Module = serve
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Start test server.
+
diff --git a/nikola/plugins/command/serve.py b/nikola/plugins/command/serve.py
new file mode 100644
index 0000000..07403d4
--- /dev/null
+++ b/nikola/plugins/command/serve.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+import os
+try:
+ from BaseHTTPServer import HTTPServer
+ from SimpleHTTPServer import SimpleHTTPRequestHandler
+except ImportError:
+ from http.server import HTTPServer # NOQA
+ from http.server import SimpleHTTPRequestHandler # NOQA
+
+from nikola.plugin_categories import Command
+from nikola.utils import get_logger
+
+
+class CommandBuild(Command):
+ """Start test server."""
+
+ name = "serve"
+ doc_usage = "[options]"
+ doc_purpose = "start the test webserver"
+ logger = None
+
+ cmd_options = (
+ {
+ 'name': 'port',
+ 'short': 'p',
+ 'long': 'port',
+ 'default': 8000,
+ 'type': int,
+ 'help': 'Port nummber (default: 8000)',
+ },
+ {
+ 'name': 'address',
+ 'short': 'a',
+ 'long': '--address',
+ 'type': str,
+ 'default': '127.0.0.1',
+ 'help': 'Address to bind (default: 127.0.0.1)',
+ },
+ )
+
+ def _execute(self, options, args):
+ """Start test server."""
+ self.logger = get_logger('serve', self.site.loghandlers)
+ out_dir = self.site.config['OUTPUT_FOLDER']
+ if not os.path.isdir(out_dir):
+ self.logger.error("Missing '{0}' folder?".format(out_dir))
+ else:
+ os.chdir(out_dir)
+ httpd = HTTPServer((options['address'], options['port']),
+ OurHTTPRequestHandler)
+ sa = httpd.socket.getsockname()
+ self.logger.notice("Serving HTTP on {0} port {1} ...".format(*sa))
+ httpd.serve_forever()
+
+
+class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
+ extensions_map = dict(SimpleHTTPRequestHandler.extensions_map)
+ extensions_map[""] = "text/plain"
+
+ # NOTICE: this is a patched version of send_head() to disable all sorts of
+ # caching. `nikola serve` is a development server, hence caching should
+ # not happen to have access to the newest resources.
+ #
+ # The original code was copy-pasted from Python 2.7. Python 3.3 contains
+ # the same code, missing the binary mode comment.
+ #
+ # Note that it might break in future versions of Python, in which case we
+ # would need to do even more magic.
+ def send_head(self):
+ """Common code for GET and HEAD commands.
+
+ This sends the response code and MIME headers.
+
+ Return value is either a file object (which has to be copied
+ to the outputfile by the caller unless the command was HEAD,
+ and must be closed by the caller under all circumstances), or
+ None, in which case the caller has nothing further to do.
+
+ """
+ path = self.translate_path(self.path)
+ f = None
+ if os.path.isdir(path):
+ if not self.path.endswith('/'):
+ # redirect browser - doing basically what apache does
+ self.send_response(301)
+ self.send_header("Location", self.path + "/")
+ # begin no-cache patch
+ # For redirects. With redirects, caching is even worse and can
+ # break more. Especially with 301 Moved Permanently redirects,
+ # like this one.
+ self.send_header("Cache-Control", "no-cache, no-store, "
+ "must-revalidate")
+ self.send_header("Pragma", "no-cache")
+ self.send_header("Expires", "0")
+ # end no-cache patch
+ self.end_headers()
+ return None
+ for index in "index.html", "index.htm":
+ index = os.path.join(path, index)
+ if os.path.exists(index):
+ path = index
+ break
+ else:
+ return self.list_directory(path)
+ ctype = self.guess_type(path)
+ try:
+ # Always read in binary mode. Opening files in text mode may cause
+ # newline translations, making the actual size of the content
+ # transmitted *less* than the content-length!
+ f = open(path, 'rb')
+ except IOError:
+ self.send_error(404, "File not found")
+ return None
+ self.send_response(200)
+ self.send_header("Content-type", ctype)
+ fs = os.fstat(f.fileno())
+ self.send_header("Content-Length", str(fs[6]))
+ self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
+ # begin no-cache patch
+ # For standard requests.
+ self.send_header("Cache-Control", "no-cache, no-store, "
+ "must-revalidate")
+ self.send_header("Pragma", "no-cache")
+ self.send_header("Expires", "0")
+ # end no-cache patch
+ self.end_headers()
+ return f
diff --git a/nikola/plugins/command/version.plugin b/nikola/plugins/command/version.plugin
new file mode 100644
index 0000000..3c1ae95
--- /dev/null
+++ b/nikola/plugins/command/version.plugin
@@ -0,0 +1,9 @@
+[Core]
+Name = version
+Module = version
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.2
+Website = http://getnikola.com
+Description = Show nikola version
diff --git a/nikola/plugins/command/version.py b/nikola/plugins/command/version.py
new file mode 100644
index 0000000..65896e9
--- /dev/null
+++ b/nikola/plugins/command/version.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+
+from nikola.plugin_categories import Command
+from nikola import __version__
+
+
+class CommandVersion(Command):
+ """Print the version."""
+
+ name = "version"
+
+ doc_usage = ""
+ needs_config = False
+ doc_purpose = "print the Nikola version number"
+
+ def _execute(self, options={}, args=None):
+ """Print the version number."""
+ print("Nikola version " + __version__)
diff --git a/nikola/plugins/command_bootswatch_theme.plugin b/nikola/plugins/command_bootswatch_theme.plugin
deleted file mode 100644
index f75f734..0000000
--- a/nikola/plugins/command_bootswatch_theme.plugin
+++ /dev/null
@@ -1,10 +0,0 @@
-[Core]
-Name = bootswatch_theme
-Module = command_bootswatch_theme
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.1
-Website = http://nikola.ralsina.com.ar
-Description = Given a swatch name and a parent theme, creates a custom theme.
-
diff --git a/nikola/plugins/command_bootswatch_theme.py b/nikola/plugins/command_bootswatch_theme.py
deleted file mode 100644
index 8400c9f..0000000
--- a/nikola/plugins/command_bootswatch_theme.py
+++ /dev/null
@@ -1,96 +0,0 @@
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import print_function
-import os
-
-try:
- import requests
-except ImportError:
- requests = None # NOQA
-
-from nikola.plugin_categories import Command
-
-
-class CommandBootswatchTheme(Command):
- """Given a swatch name and a parent theme, creates a custom theme."""
-
- name = "bootswatch_theme"
- doc_usage = "[options]"
- doc_purpose = "Given a swatch name and a parent theme, creates a custom"\
- " theme."
- cmd_options = [
- {
- 'name': 'name',
- 'short': 'n',
- 'long': 'name',
- 'default': 'custom',
- 'type': str,
- 'help': 'New theme name (default: custom)',
- },
- {
- 'name': 'swatch',
- 'short': 's',
- 'default': 'slate',
- 'type': str,
- 'help': 'Name of the swatch from bootswatch.com.'
- },
- {
- 'name': 'parent',
- 'short': 'p',
- 'long': 'parent',
- 'default': 'site',
- 'help': 'Parent theme name (default: site)',
- },
- ]
-
- def _execute(self, options, args):
- """Given a swatch name and a parent theme, creates a custom theme."""
- if requests is None:
- print('To use the install_theme command, you need to install the '
- '"requests" package.')
- return
-
- name = options['name']
- swatch = options['swatch']
- parent = options['parent']
-
- print("Creating '{0}' theme from '{1}' and '{2}'".format(name, swatch,
- parent))
- try:
- os.makedirs(os.path.join('themes', name, 'assets', 'css'))
- except:
- pass
- for fname in ('bootstrap.min.css', 'bootstrap.css'):
- url = '/'.join(('http://bootswatch.com', swatch, fname))
- print("Downloading: ", url)
- data = requests.get(url).text
- with open(os.path.join('themes', name, 'assets', 'css', fname),
- 'wb+') as output:
- output.write(data)
-
- with open(os.path.join('themes', name, 'parent'), 'wb+') as output:
- output.write(parent)
- print('Theme created. Change the THEME setting to "{0}" to use '
- 'it.'.format(name))
diff --git a/nikola/plugins/command_check.plugin b/nikola/plugins/command_check.plugin
deleted file mode 100644
index d4dcd1c..0000000
--- a/nikola/plugins/command_check.plugin
+++ /dev/null
@@ -1,10 +0,0 @@
-[Core]
-Name = check
-Module = command_check
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.1
-Website = http://nikola.ralsina.com.ar
-Description = Check the generated site
-
diff --git a/nikola/plugins/command_check.py b/nikola/plugins/command_check.py
deleted file mode 100644
index ea82703..0000000
--- a/nikola/plugins/command_check.py
+++ /dev/null
@@ -1,166 +0,0 @@
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import print_function
-import os
-import sys
-try:
- from urllib import unquote
- from urlparse import urlparse
-except ImportError:
- from urllib.parse import unquote, urlparse # NOQA
-
-import lxml.html
-
-from nikola.plugin_categories import Command
-
-
-class CommandCheck(Command):
- """Check the generated site."""
-
- name = "check"
-
- doc_usage = "-l [--find-sources] | -f"
- doc_purpose = "Check links and files in the generated site."
- cmd_options = [
- {
- 'name': 'links',
- 'short': 'l',
- 'long': 'check-links',
- 'type': bool,
- 'default': False,
- 'help': 'Check for dangling links',
- },
- {
- 'name': 'files',
- 'short': 'f',
- 'long': 'check-files',
- 'type': bool,
- 'default': False,
- 'help': 'Check for unknown files',
- },
- {
- 'name': 'find_sources',
- 'long': 'find-sources',
- 'type': bool,
- 'default': False,
- 'help': 'List possible source files for files with broken links.',
- },
- ]
-
- def _execute(self, options, args):
- """Check the generated site."""
- if not options['links'] and not options['files']:
- print(self.help())
- return False
- if options['links']:
- failure = scan_links(options['find_sources'])
- if options['files']:
- failure = scan_files()
- if failure:
- sys.exit(1)
-
-existing_targets = set([])
-
-
-def analize(task, find_sources=False):
- rv = False
- try:
- filename = task.split(":")[-1]
- d = lxml.html.fromstring(open(filename).read())
- for l in d.iterlinks():
- target = l[0].attrib[l[1]]
- if target == "#":
- continue
- parsed = urlparse(target)
- if parsed.scheme:
- continue
- if parsed.fragment:
- target = target.split('#')[0]
- target_filename = os.path.abspath(
- os.path.join(os.path.dirname(filename), unquote(target)))
- if target_filename not in existing_targets:
- if os.path.exists(target_filename):
- existing_targets.add(target_filename)
- else:
- rv = True
- print("Broken link in {0}: ".format(filename), target)
- if find_sources:
- print("Possible sources:")
- print(os.popen('nikola list --deps ' + task,
- 'r').read())
- print("===============================\n")
-
- except Exception as exc:
- print("Error with:", filename, exc)
- return rv
-
-
-def scan_links(find_sources=False):
- print("Checking Links:\n===============\n")
- failure = False
- for task in os.popen('nikola list --all', 'r').readlines():
- task = task.strip()
- if task.split(':')[0] in ('render_tags', 'render_archive',
- 'render_galleries', 'render_indexes',
- 'render_pages'
- 'render_site') and '.html' in task:
- if analize(task, find_sources):
- failure = True
- return failure
-
-
-def scan_files():
- print("Checking Files:\n===============\n")
- task_fnames = set([])
- real_fnames = set([])
- # First check that all targets are generated in the right places
- failure = False
- for task in os.popen('nikola list --all', 'r').readlines():
- task = task.strip()
- if 'output' in task and ':' in task:
- fname = task.split(':')[-1]
- task_fnames.add(fname)
- # And now check that there are no non-target files
- for root, dirs, files in os.walk('output'):
- for src_name in files:
- fname = os.path.join(root, src_name)
- real_fnames.add(fname)
-
- only_on_output = list(real_fnames - task_fnames)
- if only_on_output:
- only_on_output.sort()
- print("\nFiles from unknown origins:\n")
- for f in only_on_output:
- print(f)
- failure = True
-
- only_on_input = list(task_fnames - real_fnames)
- if only_on_input:
- only_on_input.sort()
- print("\nFiles not generated:\n")
- for f in only_on_input:
- print(f)
-
- return failure
diff --git a/nikola/plugins/command_console.plugin b/nikola/plugins/command_console.plugin
deleted file mode 100644
index 003b994..0000000
--- a/nikola/plugins/command_console.plugin
+++ /dev/null
@@ -1,9 +0,0 @@
-[Core]
-Name = console
-Module = command_console
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.1
-Website = http://nikola.ralsina.com.ar
-Description = Start a debugging python console
diff --git a/nikola/plugins/command_console.py b/nikola/plugins/command_console.py
deleted file mode 100644
index f4d0295..0000000
--- a/nikola/plugins/command_console.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import print_function, unicode_literals
-
-import os
-
-from nikola.plugin_categories import Command
-
-
-class Console(Command):
- """Start debugging console."""
- name = "console"
- shells = ['ipython', 'bpython', 'plain']
- doc_purpose = "Start an interactive python console with access to your site and configuration."
-
- def ipython(self):
- """IPython shell."""
- from nikola import Nikola
- try:
- import conf
- except ImportError:
- print("No configuration found, cannot run the console.")
- else:
- import IPython
- SITE = Nikola(**conf.__dict__)
- SITE.scan_posts()
- IPython.embed(header='Nikola Console (conf = configuration, SITE '
- '= site engine)')
-
- def bpython(self):
- """bpython shell."""
- from nikola import Nikola
- try:
- import conf
- except ImportError:
- print("No configuration found, cannot run the console.")
- else:
- import bpython
- SITE = Nikola(**conf.__dict__)
- SITE.scan_posts()
- gl = {'conf': conf, 'SITE': SITE, 'Nikola': Nikola}
- bpython.embed(banner='Nikola Console (conf = configuration, SITE '
- '= site engine)', locals_=gl)
-
- def plain(self):
- """Plain Python shell."""
- from nikola import Nikola
- try:
- import conf
- SITE = Nikola(**conf.__dict__)
- SITE.scan_posts()
- gl = {'conf': conf, 'SITE': SITE, 'Nikola': Nikola}
- except ImportError:
- print("No configuration found, cannot run the console.")
- else:
- import code
- try:
- import readline
- except ImportError:
- pass
- else:
- import rlcompleter
- readline.set_completer(rlcompleter.Completer(gl).complete)
- readline.parse_and_bind("tab:complete")
-
- pythonrc = os.environ.get("PYTHONSTARTUP")
- if pythonrc and os.path.isfile(pythonrc):
- try:
- execfile(pythonrc) # NOQA
- except NameError:
- pass
-
- code.interact(local=gl, banner='Nikola Console (conf = '
- 'configuration, SITE = site engine)')
-
- def _execute(self, options, args):
- """Start the console."""
- for shell in self.shells:
- try:
- return getattr(self, shell)()
- except ImportError:
- pass
- raise ImportError
diff --git a/nikola/plugins/command_deploy.plugin b/nikola/plugins/command_deploy.plugin
deleted file mode 100644
index c8776b5..0000000
--- a/nikola/plugins/command_deploy.plugin
+++ /dev/null
@@ -1,9 +0,0 @@
-[Core]
-Name = deploy
-Module = command_deploy
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.1
-Website = http://nikola.ralsina.com.ar
-Description = Deploy the site
diff --git a/nikola/plugins/command_deploy.py b/nikola/plugins/command_deploy.py
deleted file mode 100644
index 3277567..0000000
--- a/nikola/plugins/command_deploy.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import print_function
-from ast import literal_eval
-import codecs
-from datetime import datetime
-import os
-import subprocess
-
-
-from nikola.plugin_categories import Command
-
-
-class Deploy(Command):
- """Deploy site. """
- name = "deploy"
-
- doc_usage = ""
- doc_purpose = "Deploy the site."
-
- def _execute(self, command, args):
- for command in self.site.config['DEPLOY_COMMANDS']:
-
- # Get last succesful deploy date
- timestamp_path = os.path.join(self.site.config['CACHE_FOLDER'], 'lastdeploy')
- try:
- with open(timestamp_path, 'rb') as inf:
- last_deploy = literal_eval(inf.read().strip())
- except Exception:
- last_deploy = datetime(1970, 1, 1) # NOQA
-
- print("==>", command)
- ret = subprocess.check_call(command, shell=True)
- if ret != 0: # failed deployment
- raise Exception("Failed deployment")
- print("Successful deployment")
- new_deploy = datetime.now()
- # Store timestamp of successful deployment
- with codecs.open(timestamp_path, 'wb+', 'utf8') as outf:
- outf.write(repr(new_deploy))
- # Here is where we would do things with whatever is
- # on self.site.timeline and is newer than
- # last_deploy
diff --git a/nikola/plugins/command_import_blogger.plugin b/nikola/plugins/command_import_blogger.plugin
deleted file mode 100644
index b275a7f..0000000
--- a/nikola/plugins/command_import_blogger.plugin
+++ /dev/null
@@ -1,10 +0,0 @@
-[Core]
-Name = import_blogger
-Module = command_import_blogger
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.2
-Website = http://nikola.ralsina.com.ar
-Description = Import a blogger site from a XML dump.
-
diff --git a/nikola/plugins/command_import_blogger.py b/nikola/plugins/command_import_blogger.py
deleted file mode 100644
index ecc4676..0000000
--- a/nikola/plugins/command_import_blogger.py
+++ /dev/null
@@ -1,308 +0,0 @@
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import unicode_literals, print_function
-import codecs
-import csv
-import datetime
-import os
-import time
-
-try:
- from urlparse import urlparse
-except ImportError:
- from urllib.parse import urlparse # NOQA
-
-try:
- import feedparser
-except ImportError:
- feedparser = None # NOQA
-from lxml import html
-from mako.template import Template
-
-from nikola.plugin_categories import Command
-from nikola import utils
-
-links = {}
-
-
-class CommandImportBlogger(Command):
- """Import a blogger dump."""
-
- name = "import_blogger"
- needs_config = False
- doc_usage = "[options] blogger_export_file"
- doc_purpose = "Import a blogger dump."
- cmd_options = [
- {
- 'name': 'output_folder',
- 'long': 'output-folder',
- 'short': 'o',
- 'default': 'new_site',
- 'help': 'Location to write imported content.'
- },
- {
- 'name': 'exclude_drafts',
- 'long': 'no-drafts',
- 'short': 'd',
- 'default': False,
- 'type': bool,
- 'help': "Don't import drafts",
- },
- ]
-
- def _execute(self, options, args):
- """Import a Blogger blog from an export file into a Nikola site."""
-
- # Parse the data
- if feedparser is None:
- print('To use the import_blogger command,'
- ' you have to install the "feedparser" package.')
- return
-
- if not args:
- print(self.help())
- return
-
- options['filename'] = args[0]
- self.blogger_export_file = options['filename']
- self.output_folder = options['output_folder']
- self.import_into_existing_site = False
- self.exclude_drafts = options['exclude_drafts']
- self.url_map = {}
- channel = self.get_channel_from_file(self.blogger_export_file)
- self.context = self.populate_context(channel)
- conf_template = self.generate_base_site()
- self.context['REDIRECTIONS'] = self.configure_redirections(
- self.url_map)
-
- self.import_posts(channel)
- self.write_urlmap_csv(
- os.path.join(self.output_folder, 'url_map.csv'), self.url_map)
-
- self.write_configuration(self.get_configuration_output_path(
- ), conf_template.render(**self.context))
-
- @classmethod
- def get_channel_from_file(cls, filename):
- if not os.path.isfile(filename):
- raise Exception("Missing file: %s" % filename)
- return feedparser.parse(filename)
-
- @staticmethod
- def configure_redirections(url_map):
- redirections = []
- for k, v in url_map.items():
- # remove the initial "/" because src is a relative file path
- src = (urlparse(k).path + 'index.html')[1:]
- dst = (urlparse(v).path)
- if src == 'index.html':
- print("Can't do a redirect for: {0!r}".format(k))
- else:
- redirections.append((src, dst))
-
- return redirections
-
- def generate_base_site(self):
- if not os.path.exists(self.output_folder):
- os.system('nikola init ' + self.output_folder)
- else:
- self.import_into_existing_site = True
- print('The folder {0} already exists - assuming that this is a '
- 'already existing nikola site.'.format(self.output_folder))
-
- conf_template = Template(filename=os.path.join(
- os.path.dirname(utils.__file__), 'conf.py.in'))
-
- return conf_template
-
- @staticmethod
- def populate_context(channel):
- context = {}
- context['DEFAULT_LANG'] = 'en' # blogger doesn't include the language
- # in the dump
- context['BLOG_TITLE'] = channel.feed.title
-
- context['BLOG_DESCRIPTION'] = '' # Missing in the dump
- context['SITE_URL'] = channel.feed.link.rstrip('/')
- context['BLOG_EMAIL'] = channel.feed.author_detail.email
- context['BLOG_AUTHOR'] = channel.feed.author_detail.name
- context['POST_PAGES'] = '''(
- ("posts/*.html", "posts", "post.tmpl", True),
- ("stories/*.html", "stories", "story.tmpl", False),
- )'''
- context['POST_COMPILERS'] = '''{
- "rest": ('.txt', '.rst'),
- "markdown": ('.md', '.mdown', '.markdown', '.wp'),
- "html": ('.html', '.htm')
- }
- '''
-
- return context
-
- @classmethod
- def transform_content(cls, content):
- # No transformations yet
- return content
-
- @classmethod
- def write_content(cls, filename, content):
- doc = html.document_fromstring(content)
- doc.rewrite_links(replacer)
-
- with open(filename, "wb+") as fd:
- fd.write(html.tostring(doc, encoding='utf8'))
-
- @staticmethod
- def write_metadata(filename, title, slug, post_date, description, tags):
- if not description:
- description = ""
-
- with codecs.open(filename, "w+", "utf8") as fd:
- fd.write('{0}\n'.format(title))
- fd.write('{0}\n'.format(slug))
- fd.write('{0}\n'.format(post_date))
- fd.write('{0}\n'.format(','.join(tags)))
- fd.write('\n')
- fd.write('{0}\n'.format(description))
-
- def import_item(self, item, out_folder=None):
- """Takes an item from the feed and creates a post file."""
- if out_folder is None:
- out_folder = 'posts'
-
- # link is something like http://foo.com/2012/09/01/hello-world/
- # So, take the path, utils.slugify it, and that's our slug
- link = item.link
- link_path = urlparse(link).path
-
- title = item.title
-
- # blogger supports empty titles, which Nikola doesn't
- if not title:
- print("Warning: Empty title in post with URL {0}. Using NO_TITLE "
- "as placeholder, please fix.".format(link))
- title = "NO_TITLE"
-
- if link_path.lower().endswith('.html'):
- link_path = link_path[:-5]
-
- slug = utils.slugify(link_path)
-
- if not slug: # should never happen
- print("Error converting post:", title)
- return
-
- description = ''
- post_date = datetime.datetime.fromtimestamp(time.mktime(
- item.published_parsed))
-
- for candidate in item.content:
- if candidate.type == 'text/html':
- content = candidate.value
- break
- # FIXME: handle attachments
-
- tags = []
- for tag in item.tags:
- if tag.scheme == 'http://www.blogger.com/atom/ns#':
- tags.append(tag.term)
-
- if item.get('app_draft'):
- tags.append('draft')
- is_draft = True
- else:
- is_draft = False
-
- self.url_map[link] = self.context['SITE_URL'] + '/' + \
- out_folder + '/' + slug + '.html'
-
- if is_draft and self.exclude_drafts:
- print('Draft "{0}" will not be imported.'.format(title))
- elif content.strip():
- # If no content is found, no files are written.
- content = self.transform_content(content)
-
- self.write_metadata(os.path.join(self.output_folder, out_folder,
- slug + '.meta'),
- title, slug, post_date, description, tags)
- self.write_content(
- os.path.join(self.output_folder, out_folder, slug + '.html'),
- content)
- else:
- print('Not going to import "{0}" because it seems to contain'
- ' no content.'.format(title))
-
- def process_item(self, item):
- post_type = item.tags[0].term
-
- if post_type == 'http://schemas.google.com/blogger/2008/kind#post':
- self.import_item(item, 'posts')
- elif post_type == 'http://schemas.google.com/blogger/2008/kind#page':
- self.import_item(item, 'stories')
- elif post_type == ('http://schemas.google.com/blogger/2008/kind'
- '#settings'):
- # Ignore settings
- pass
- elif post_type == ('http://schemas.google.com/blogger/2008/kind'
- '#template'):
- # Ignore template
- pass
- elif post_type == ('http://schemas.google.com/blogger/2008/kind'
- '#comment'):
- # FIXME: not importing comments. Does blogger support "pages"?
- pass
- else:
- print("Unknown post_type:", post_type)
-
- def import_posts(self, channel):
- for item in channel.entries:
- self.process_item(item)
-
- @staticmethod
- def write_urlmap_csv(output_file, url_map):
- with codecs.open(output_file, 'w+', 'utf8') as fd:
- csv_writer = csv.writer(fd)
- for item in url_map.items():
- csv_writer.writerow(item)
-
- def get_configuration_output_path(self):
- if not self.import_into_existing_site:
- filename = 'conf.py'
- else:
- filename = 'conf.py.blogger_import-{0}'.format(
- datetime.datetime.now().strftime('%Y%m%d_%H%M%s'))
- config_output_path = os.path.join(self.output_folder, filename)
- print('Configuration will be written to: ' + config_output_path)
-
- return config_output_path
-
- @staticmethod
- def write_configuration(filename, rendered_template):
- with codecs.open(filename, 'w+', 'utf8') as fd:
- fd.write(rendered_template)
-
-
-def replacer(dst):
- return links.get(dst, dst)
diff --git a/nikola/plugins/command_import_wordpress.plugin b/nikola/plugins/command_import_wordpress.plugin
deleted file mode 100644
index ff7cdca..0000000
--- a/nikola/plugins/command_import_wordpress.plugin
+++ /dev/null
@@ -1,10 +0,0 @@
-[Core]
-Name = import_wordpress
-Module = command_import_wordpress
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.2
-Website = http://nikola.ralsina.com.ar
-Description = Import a wordpress site from a XML dump (requires markdown).
-
diff --git a/nikola/plugins/command_import_wordpress.py b/nikola/plugins/command_import_wordpress.py
deleted file mode 100644
index b45fe78..0000000
--- a/nikola/plugins/command_import_wordpress.py
+++ /dev/null
@@ -1,439 +0,0 @@
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import unicode_literals, print_function
-import codecs
-import csv
-import datetime
-import os
-import re
-
-try:
- from urlparse import urlparse
-except ImportError:
- from urllib.parse import urlparse # NOQA
-
-from lxml import etree, html
-from mako.template import Template
-
-try:
- import requests
-except ImportError:
- requests = None # NOQA
-
-from nikola.plugin_categories import Command
-from nikola import utils
-
-links = {}
-
-
-class CommandImportWordpress(Command):
- """Import a wordpress dump."""
-
- name = "import_wordpress"
- needs_config = False
- doc_usage = "[options] wordpress_export_file"
- doc_purpose = "Import a wordpress dump."
- cmd_options = [
- {
- 'name': 'output_folder',
- 'long': 'output-folder',
- 'short': 'o',
- 'default': 'new_site',
- 'help': 'Location to write imported content.'
- },
- {
- 'name': 'exclude_drafts',
- 'long': 'no-drafts',
- 'short': 'd',
- 'default': False,
- 'type': bool,
- 'help': "Don't import drafts",
- },
- {
- 'name': 'squash_newlines',
- 'long': 'squash-newlines',
- 'default': False,
- 'type': bool,
- 'help': "Shorten multiple newlines in a row to only two newlines",
- },
- {
- 'name': 'no_downloads',
- 'long': 'no-downloads',
- 'default': False,
- 'type': bool,
- 'help': "Do not try to download files for the import",
- },
- ]
-
- def _execute(self, options={}, args=[]):
- """Import a Wordpress blog from an export file into a Nikola site."""
- # Parse the data
- if requests is None:
- print('To use the import_wordpress command,'
- ' you have to install the "requests" package.')
- return
-
- if not args:
- print(self.help())
- return
-
- options['filename'] = args.pop(0)
-
- if args and ('output_folder' not in args or
- options['output_folder'] == 'new_site'):
- options['output_folder'] = args.pop(0)
-
- if args:
- print('You specified additional arguments ({0}). Please consider '
- 'putting these arguments before the filename if you '
- 'are running into problems.'.format(args))
-
- self.wordpress_export_file = options['filename']
- self.squash_newlines = options.get('squash_newlines', False)
- self.no_downloads = options.get('no_downloads', False)
- self.output_folder = options.get('output_folder', 'new_site')
- self.import_into_existing_site = False
- self.exclude_drafts = options.get('exclude_drafts', False)
- self.url_map = {}
- channel = self.get_channel_from_file(self.wordpress_export_file)
- self.context = self.populate_context(channel)
- conf_template = self.generate_base_site()
-
- self.import_posts(channel)
-
- self.context['REDIRECTIONS'] = self.configure_redirections(
- self.url_map)
- self.write_urlmap_csv(
- os.path.join(self.output_folder, 'url_map.csv'), self.url_map)
- rendered_template = conf_template.render(**self.context)
- rendered_template = re.sub('# REDIRECTIONS = ', 'REDIRECTIONS = ',
- rendered_template)
- self.write_configuration(self.get_configuration_output_path(),
- rendered_template)
-
- @classmethod
- def _glue_xml_lines(cls, xml):
- new_xml = xml[0]
- previous_line_ended_in_newline = new_xml.endswith(b'\n')
- previous_line_was_indentet = False
- for line in xml[1:]:
- if (re.match(b'^[ \t]+', line) and previous_line_ended_in_newline):
- new_xml = b''.join((new_xml, line))
- previous_line_was_indentet = True
- elif previous_line_was_indentet:
- new_xml = b''.join((new_xml, line))
- previous_line_was_indentet = False
- else:
- new_xml = b'\n'.join((new_xml, line))
- previous_line_was_indentet = False
-
- previous_line_ended_in_newline = line.endswith(b'\n')
-
- return new_xml
-
- @classmethod
- def read_xml_file(cls, filename):
- xml = []
-
- with open(filename, 'rb') as fd:
- for line in fd:
- # These explode etree and are useless
- if b' {1}".format(url, dst_path))
- self.download_url_content_to_file(url, dst_path)
- dst_url = '/'.join(dst_path.split(os.sep)[2:])
- links[link] = '/' + dst_url
- links[url] = '/' + dst_url
-
- @staticmethod
- def transform_sourcecode(content):
- new_content = re.sub('\[sourcecode language="([^"]+)"\]',
- "\n~~~~~~~~~~~~{.\\1}\n", content)
- new_content = new_content.replace('[/sourcecode]',
- "\n~~~~~~~~~~~~\n")
- return new_content
-
- @staticmethod
- def transform_caption(content):
- new_caption = re.sub(r'\[/caption\]', '', content)
- new_caption = re.sub(r'\[caption.*\]', '', new_caption)
-
- return new_caption
-
- def transform_multiple_newlines(self, content):
- """Replaces multiple newlines with only two."""
- if self.squash_newlines:
- return re.sub(r'\n{3,}', r'\n\n', content)
- else:
- return content
-
- def transform_content(self, content):
- new_content = self.transform_sourcecode(content)
- new_content = self.transform_caption(new_content)
- new_content = self.transform_multiple_newlines(new_content)
- return new_content
-
- @classmethod
- def write_content(cls, filename, content):
- doc = html.document_fromstring(content)
- doc.rewrite_links(replacer)
-
- with open(filename, "wb+") as fd:
- fd.write(html.tostring(doc, encoding='utf8'))
-
- @staticmethod
- def write_metadata(filename, title, slug, post_date, description, tags):
- if not description:
- description = ""
-
- with codecs.open(filename, "w+", "utf8") as fd:
- fd.write('{0}\n'.format(title))
- fd.write('{0}\n'.format(slug))
- fd.write('{0}\n'.format(post_date))
- fd.write('{0}\n'.format(','.join(tags)))
- fd.write('\n')
- fd.write('{0}\n'.format(description))
-
- def import_item(self, item, wordpress_namespace, out_folder=None):
- """Takes an item from the feed and creates a post file."""
- if out_folder is None:
- out_folder = 'posts'
-
- title = get_text_tag(item, 'title', 'NO TITLE')
- # link is something like http://foo.com/2012/09/01/hello-world/
- # So, take the path, utils.slugify it, and that's our slug
- link = get_text_tag(item, 'link', None)
- path = urlparse(link).path
-
- # In python 2, path is a str. slug requires a unicode
- # object. Luckily, paths are also ASCII
- if isinstance(path, utils.bytes_str):
- path = path.decode('ASCII')
- slug = utils.slugify(path)
- if not slug: # it happens if the post has no "nice" URL
- slug = get_text_tag(
- item, '{{{0}}}post_name'.format(wordpress_namespace), None)
- if not slug: # it *may* happen
- slug = get_text_tag(
- item, '{{{0}}}post_id'.format(wordpress_namespace), None)
- if not slug: # should never happen
- print("Error converting post:", title)
- return
-
- description = get_text_tag(item, 'description', '')
- post_date = get_text_tag(
- item, '{{{0}}}post_date'.format(wordpress_namespace), None)
- status = get_text_tag(
- item, '{{{0}}}status'.format(wordpress_namespace), 'publish')
- content = get_text_tag(
- item, '{http://purl.org/rss/1.0/modules/content/}encoded', '')
-
- tags = []
- if status == 'trash':
- print('Trashed post "{0}" will not be imported.'.format(title))
- return
- elif status != 'publish':
- tags.append('draft')
- is_draft = True
- else:
- is_draft = False
-
- for tag in item.findall('category'):
- text = tag.text
- if text == 'Uncategorized':
- continue
- tags.append(text)
-
- if is_draft and self.exclude_drafts:
- print('Draft "{0}" will not be imported.'.format(title))
- elif content.strip():
- # If no content is found, no files are written.
- self.url_map[link] = self.context['SITE_URL'] + '/' + \
- out_folder + '/' + slug + '.html'
-
- content = self.transform_content(content)
-
- self.write_metadata(os.path.join(self.output_folder, out_folder,
- slug + '.meta'),
- title, slug, post_date, description, tags)
- self.write_content(
- os.path.join(self.output_folder, out_folder, slug + '.wp'),
- content)
- else:
- print('Not going to import "{0}" because it seems to contain'
- ' no content.'.format(title))
-
- def process_item(self, item):
- # The namespace usually is something like:
- # http://wordpress.org/export/1.2/
- wordpress_namespace = item.nsmap['wp']
- post_type = get_text_tag(
- item, '{{{0}}}post_type'.format(wordpress_namespace), 'post')
-
- if post_type == 'attachment':
- self.import_attachment(item, wordpress_namespace)
- elif post_type == 'post':
- self.import_item(item, wordpress_namespace, 'posts')
- else:
- self.import_item(item, wordpress_namespace, 'stories')
-
- def import_posts(self, channel):
- for item in channel.findall('item'):
- self.process_item(item)
-
- @staticmethod
- def write_urlmap_csv(output_file, url_map):
- with codecs.open(output_file, 'w+', 'utf8') as fd:
- csv_writer = csv.writer(fd)
- for item in url_map.items():
- csv_writer.writerow(item)
-
- def get_configuration_output_path(self):
- if not self.import_into_existing_site:
- filename = 'conf.py'
- else:
- filename = 'conf.py.wordpress_import-{0}'.format(
- datetime.datetime.now().strftime('%Y%m%d_%H%M%s'))
- config_output_path = os.path.join(self.output_folder, filename)
- print('Configuration will be written to:', config_output_path)
-
- return config_output_path
-
- @staticmethod
- def write_configuration(filename, rendered_template):
- with codecs.open(filename, 'w+', 'utf8') as fd:
- fd.write(rendered_template)
-
-
-def replacer(dst):
- return links.get(dst, dst)
-
-
-def get_text_tag(tag, name, default):
- if tag is None:
- return default
- t = tag.find(name)
- if t is not None:
- return t.text
- else:
- return default
diff --git a/nikola/plugins/command_init.plugin b/nikola/plugins/command_init.plugin
deleted file mode 100644
index f4adf4a..0000000
--- a/nikola/plugins/command_init.plugin
+++ /dev/null
@@ -1,9 +0,0 @@
-[Core]
-Name = init
-Module = command_init
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.2
-Website = http://nikola.ralsina.com.ar
-Description = Create a new site.
diff --git a/nikola/plugins/command_init.py b/nikola/plugins/command_init.py
deleted file mode 100644
index bc36266..0000000
--- a/nikola/plugins/command_init.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import print_function
-import os
-import shutil
-import codecs
-
-from mako.template import Template
-
-import nikola
-from nikola.plugin_categories import Command
-
-
-class CommandInit(Command):
- """Create a new site."""
-
- name = "init"
-
- doc_usage = "[--demo] folder"
- needs_config = False
- doc_purpose = """Create a Nikola site in the specified folder."""
- cmd_options = [
- {
- 'name': 'demo',
- 'long': 'demo',
- 'default': False,
- 'type': bool,
- 'help': "Create a site filled with example data.",
- }
- ]
-
- SAMPLE_CONF = {
- 'BLOG_AUTHOR': "Your Name",
- 'BLOG_TITLE': "Demo Site",
- 'SITE_URL': "http://nikola.ralsina.com.ar",
- 'BLOG_EMAIL': "joe@demo.site",
- 'BLOG_DESCRIPTION': "This is a demo site for Nikola.",
- 'DEFAULT_LANG': "en",
-
- 'POST_PAGES': """(
- ("posts/*.txt", "posts", "post.tmpl", True),
- ("stories/*.txt", "stories", "story.tmpl", False),
-)""",
-
- 'POST_COMPILERS': """{
- "rest": ('.txt', '.rst'),
- "markdown": ('.md', '.mdown', '.markdown'),
- "textile": ('.textile',),
- "txt2tags": ('.t2t',),
- "bbcode": ('.bb',),
- "wiki": ('.wiki',),
- "ipynb": ('.ipynb',),
- "html": ('.html', '.htm')
-}""",
- 'REDIRECTIONS': '[]',
- }
-
- @classmethod
- def copy_sample_site(cls, target):
- lib_path = cls.get_path_to_nikola_modules()
- src = os.path.join(lib_path, 'data', 'samplesite')
- shutil.copytree(src, target)
-
- @classmethod
- def create_configuration(cls, target):
- lib_path = cls.get_path_to_nikola_modules()
- template_path = os.path.join(lib_path, 'conf.py.in')
- conf_template = Template(filename=template_path)
- conf_path = os.path.join(target, 'conf.py')
- with codecs.open(conf_path, 'w+', 'utf8') as fd:
- fd.write(conf_template.render(**cls.SAMPLE_CONF))
-
- @classmethod
- def create_empty_site(cls, target):
- for folder in ('files', 'galleries', 'listings', 'posts', 'stories'):
- os.makedirs(os.path.join(target, folder))
-
- @staticmethod
- def get_path_to_nikola_modules():
- return os.path.dirname(nikola.__file__)
-
- def _execute(self, options={}, args=None):
- """Create a new site."""
- if not args:
- print("Usage: nikola init folder [options]")
- return False
- target = args[0]
- if target is None:
- print(self.usage)
- else:
- if not options or not options.get('demo'):
- self.create_empty_site(target)
- print('Created empty site at {0}.'.format(target))
- else:
- self.copy_sample_site(target)
- print("A new site with example data has been created at "
- "{0}.".format(target))
- print("See README.txt in that folder for more information.")
-
- self.create_configuration(target)
diff --git a/nikola/plugins/command_install_theme.plugin b/nikola/plugins/command_install_theme.plugin
deleted file mode 100644
index f010074..0000000
--- a/nikola/plugins/command_install_theme.plugin
+++ /dev/null
@@ -1,10 +0,0 @@
-[Core]
-Name = install_theme
-Module = command_install_theme
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.1
-Website = http://nikola.ralsina.com.ar
-Description = Install a theme into the current site.
-
diff --git a/nikola/plugins/command_install_theme.py b/nikola/plugins/command_install_theme.py
deleted file mode 100644
index 2a0a0cc..0000000
--- a/nikola/plugins/command_install_theme.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import print_function
-import os
-import json
-from io import BytesIO
-
-try:
- import requests
-except ImportError:
- requests = None # NOQA
-
-from nikola.plugin_categories import Command
-from nikola import utils
-
-
-class CommandInstallTheme(Command):
- """Start test server."""
-
- name = "install_theme"
- doc_usage = "[[-u] theme_name] | [[-u] -l]"
- doc_purpose = "Install theme into current site."
- cmd_options = [
- {
- 'name': 'list',
- 'short': 'l',
- 'long': 'list',
- 'type': bool,
- 'default': False,
- 'help': 'Show list of available themes.'
- },
- {
- 'name': 'url',
- 'short': 'u',
- 'long': 'url',
- 'type': str,
- 'help': "URL for the theme repository (default: "
- "http://nikola.ralsina.com.ar/themes/index.json)",
- 'default': 'http://nikola.ralsina.com.ar/themes/index.json'
- },
- ]
-
- def _execute(self, options, args):
- """Install theme into current site."""
- if requests is None:
- print('This command requires the requests package be installed.')
- return False
-
- listing = options['list']
- url = options['url']
- if args:
- name = args[0]
- else:
- name = None
-
- if name is None and not listing:
- print("This command needs either a theme name or the -l option.")
- return False
- data = requests.get(url).text
- data = json.loads(data)
- if listing:
- print("Themes:")
- print("-------")
- for theme in sorted(data.keys()):
- print(theme)
- return True
- else:
- if name in data:
- if os.path.isfile("themes"):
- raise IOError("'themes' isn't a directory!")
- elif not os.path.isdir("themes"):
- try:
- os.makedirs("themes")
- except:
- raise OSError("mkdir 'theme' error!")
- print('Downloading: ' + data[name])
- zip_file = BytesIO()
- zip_file.write(requests.get(data[name]).content)
- print('Extracting: {0} into themes'.format(name))
- utils.extract_all(zip_file)
- else:
- print("Can't find theme " + name)
- return False
diff --git a/nikola/plugins/command_new_post.plugin b/nikola/plugins/command_new_post.plugin
deleted file mode 100644
index 6d70aff..0000000
--- a/nikola/plugins/command_new_post.plugin
+++ /dev/null
@@ -1,10 +0,0 @@
-[Core]
-Name = new_post
-Module = command_new_post
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.1
-Website = http://nikola.ralsina.com.ar
-Description = Create a new post.
-
diff --git a/nikola/plugins/command_new_post.py b/nikola/plugins/command_new_post.py
deleted file mode 100644
index 933a51a..0000000
--- a/nikola/plugins/command_new_post.py
+++ /dev/null
@@ -1,225 +0,0 @@
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import unicode_literals, print_function
-import codecs
-import datetime
-import os
-import sys
-
-from nikola.plugin_categories import Command
-from nikola import utils
-
-
-def filter_post_pages(compiler, is_post, post_compilers, post_pages):
- """Given a compiler ("markdown", "rest"), and whether it's meant for
- a post or a page, and post_compilers, return the correct entry from
- post_pages."""
-
- # First throw away all the post_pages with the wrong is_post
- filtered = [entry for entry in post_pages if entry[3] == is_post]
-
- # These are the extensions supported by the required format
- extensions = post_compilers[compiler]
-
- # Throw away the post_pages with the wrong extensions
- filtered = [entry for entry in filtered if any([ext in entry[0] for ext in
- extensions])]
-
- if not filtered:
- type_name = "post" if is_post else "page"
- raise Exception("Can't find a way, using your configuration, to create "
- "a {0} in format {1}. You may want to tweak "
- "post_compilers or post_pages in conf.py".format(
- type_name, compiler))
- return filtered[0]
-
-
-def get_default_compiler(is_post, post_compilers, post_pages):
- """Given post_compilers and post_pages, return a reasonable
- default compiler for this kind of post/page.
- """
-
- # First throw away all the post_pages with the wrong is_post
- filtered = [entry for entry in post_pages if entry[3] == is_post]
-
- # Get extensions in filtered post_pages until one matches a compiler
- for entry in filtered:
- extension = os.path.splitext(entry[0])[-1]
- for compiler, extensions in post_compilers.items():
- if extension in extensions:
- return compiler
- # No idea, back to default behaviour
- return 'rest'
-
-
-class CommandNewPost(Command):
- """Create a new post."""
-
- name = "new_post"
- doc_usage = "[options] [path]"
- doc_purpose = "Create a new blog post or site page."
- cmd_options = [
- {
- 'name': 'is_page',
- 'short': 'p',
- 'long': 'page',
- 'type': bool,
- 'default': False,
- 'help': 'Create a page instead of a blog post.'
- },
- {
- 'name': 'title',
- 'short': 't',
- 'long': 'title',
- 'type': str,
- 'default': '',
- 'help': 'Title for the page/post.'
- },
- {
- 'name': 'tags',
- 'long': 'tags',
- 'type': str,
- 'default': '',
- 'help': 'Comma-separated tags for the page/post.'
- },
- {
- 'name': 'onefile',
- 'short': '1',
- 'type': bool,
- 'default': False,
- 'help': 'Create post with embedded metadata (single file format)'
- },
- {
- 'name': 'twofile',
- 'short': '2',
- 'type': bool,
- 'default': False,
- 'help': 'Create post with separate metadata (two file format)'
- },
- {
- 'name': 'post_format',
- 'short': 'f',
- 'long': 'format',
- 'type': str,
- 'default': '',
- 'help': 'Markup format for post, one of rest, markdown, wiki, '
- 'bbcode, html, textile, txt2tags',
- }
- ]
-
- def _execute(self, options, args):
- """Create a new post or page."""
-
- compiler_names = [p.name for p in
- self.site.plugin_manager.getPluginsOfCategory(
- "PageCompiler")]
-
- if len(args) > 1:
- print(self.help())
- return False
- elif args:
- path = args[0]
- else:
- path = None
-
- is_page = options.get('is_page', False)
- is_post = not is_page
- title = options['title'] or None
- tags = options['tags']
- onefile = options['onefile']
- twofile = options['twofile']
-
- if twofile:
- onefile = False
- if not onefile and not twofile:
- onefile = self.site.config.get('ONE_FILE_POSTS', True)
-
- post_format = options['post_format']
-
- if not post_format: # Issue #400
- post_format = get_default_compiler(
- is_post,
- self.site.config['post_compilers'],
- self.site.config['post_pages'])
-
- if post_format not in compiler_names:
- print("ERROR: Unknown post format " + post_format)
- return
- compiler_plugin = self.site.plugin_manager.getPluginByName(
- post_format, "PageCompiler").plugin_object
-
- # Guess where we should put this
- entry = filter_post_pages(post_format, is_post,
- self.site.config['post_compilers'],
- self.site.config['post_pages'])
-
- print("Creating New Post")
- print("-----------------\n")
- if title is None:
- print("Enter title: ", end='')
- # WHY, PYTHON3???? WHY?
- sys.stdout.flush()
- title = sys.stdin.readline()
- else:
- print("Title:", title)
- if isinstance(title, utils.bytes_str):
- title = title.decode(sys.stdin.encoding)
- title = title.strip()
- if not path:
- slug = utils.slugify(title)
- else:
- if isinstance(path, utils.bytes_str):
- path = path.decode(sys.stdin.encoding)
- slug = utils.slugify(os.path.splitext(os.path.basename(path))[0])
- date = datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')
- data = [title, slug, date, tags]
- output_path = os.path.dirname(entry[0])
- meta_path = os.path.join(output_path, slug + ".meta")
- pattern = os.path.basename(entry[0])
- suffix = pattern[1:]
- if not path:
- txt_path = os.path.join(output_path, slug + suffix)
- else:
- txt_path = path
-
- if (not onefile and os.path.isfile(meta_path)) or \
- os.path.isfile(txt_path):
- print("The title already exists!")
- exit()
-
- d_name = os.path.dirname(txt_path)
- if not os.path.exists(d_name):
- os.makedirs(d_name)
- compiler_plugin.create_post(
- txt_path, onefile, title=title,
- slug=slug, date=date, tags=tags)
-
- if not onefile: # write metadata file
- with codecs.open(meta_path, "wb+", "utf8") as fd:
- fd.write('\n'.join(data))
- with codecs.open(txt_path, "wb+", "utf8") as fd:
- fd.write("Write your post here.")
- print("Your post's metadata is at: ", meta_path)
- print("Your post's text is at: ", txt_path)
diff --git a/nikola/plugins/command_planetoid.plugin b/nikola/plugins/command_planetoid.plugin
deleted file mode 100644
index 8636d49..0000000
--- a/nikola/plugins/command_planetoid.plugin
+++ /dev/null
@@ -1,9 +0,0 @@
-[Core]
-Name = planetoid
-Module = command_planetoid
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.1
-Website = http://nikola.ralsina.com.ar
-Description = Maintain a planet-like site
diff --git a/nikola/plugins/command_planetoid/__init__.py b/nikola/plugins/command_planetoid/__init__.py
deleted file mode 100644
index 183dd51..0000000
--- a/nikola/plugins/command_planetoid/__init__.py
+++ /dev/null
@@ -1,287 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import print_function, unicode_literals
-import codecs
-import datetime
-import hashlib
-from optparse import OptionParser
-import os
-import sys
-
-from doit.tools import timeout
-from nikola.plugin_categories import Command, Task
-from nikola.utils import config_changed
-
-try:
- import feedparser
-except ImportError:
- feedparser = None # NOQA
-
-try:
- import peewee
-except ImportError:
- peewee = None
-
-
-if peewee is not None:
- class Feed(peewee.Model):
- name = peewee.CharField()
- url = peewee.CharField(max_length=200)
- last_status = peewee.CharField(null=True)
- etag = peewee.CharField(max_length=200)
- last_modified = peewee.DateTimeField()
-
- class Entry(peewee.Model):
- date = peewee.DateTimeField()
- feed = peewee.ForeignKeyField(Feed)
- content = peewee.TextField(max_length=20000)
- link = peewee.CharField(max_length=200)
- title = peewee.CharField(max_length=200)
- guid = peewee.CharField(max_length=200)
-
-
-class Planetoid(Command, Task):
- """Maintain a planet-like thing."""
- name = "planetoid"
-
- def init_db(self):
- # setup database
- Feed.create_table(fail_silently=True)
- Entry.create_table(fail_silently=True)
-
- def gen_tasks(self):
- if peewee is None or sys.version_info[0] == 3:
- if sys.version_info[0] == 3:
- message = 'Peewee is currently incompatible with Python 3.'
- else:
- message = 'You need to install the \"peewee\" module.'
-
- yield {
- 'basename': self.name,
- 'name': '',
- 'verbosity': 2,
- 'actions': ['echo "%s"' % message]
- }
- else:
- self.init_db()
- self.load_feeds()
- for task in self.task_update_feeds():
- yield task
- for task in self.task_generate_posts():
- yield task
- yield {
- 'basename': self.name,
- 'name': '',
- 'actions': [],
- 'file_dep': ['feeds'],
- 'task_dep': [
- self.name + "_fetch_feed",
- self.name + "_generate_posts",
- ]
- }
-
- def run(self, *args):
- parser = OptionParser(usage="nikola %s [options]" % self.name)
- (options, args) = parser.parse_args(list(args))
-
- def load_feeds(self):
- "Read the feeds file, add it to the database."
- feeds = []
- feed = name = None
- for line in codecs.open('feeds', 'r', 'utf-8'):
- line = line.strip()
- if line.startswith("#"):
- continue
- elif line.startswith('http'):
- feed = line
- elif line:
- name = line
- if feed and name:
- feeds.append([feed, name])
- feed = name = None
-
- def add_feed(name, url):
- f = Feed.create(
- name=name,
- url=url,
- etag='foo',
- last_modified=datetime.datetime(1970, 1, 1),
- )
- f.save()
-
- def update_feed_url(feed, url):
- feed.url = url
- feed.save()
-
- for feed, name in feeds:
- f = Feed.select().where(Feed.name == name)
- if not list(f):
- add_feed(name, feed)
- elif list(f)[0].url != feed:
- update_feed_url(list(f)[0], feed)
-
- def task_update_feeds(self):
- """Download feed contents, add entries to the database."""
- def update_feed(feed):
- modified = feed.last_modified.timetuple()
- etag = feed.etag
- try:
- parsed = feedparser.parse(
- feed.url,
- etag=etag,
- modified=modified
- )
- feed.last_status = str(parsed.status)
- except: # Probably a timeout
- # TODO: log failure
- return
- if parsed.feed.get('title'):
- print(parsed.feed.title)
- else:
- print(feed.url)
- feed.etag = parsed.get('etag', 'foo')
- modified = tuple(parsed.get('date_parsed', (1970, 1, 1)))[:6]
- print("==========>", modified)
- modified = datetime.datetime(*modified)
- feed.last_modified = modified
- feed.save()
- # No point in adding items from missinfg feeds
- if parsed.status > 400:
- # TODO log failure
- return
- for entry_data in parsed.entries:
- print("=========================================")
- date = entry_data.get('published_parsed', None)
- if date is None:
- date = entry_data.get('updated_parsed', None)
- if date is None:
- print("Can't parse date from:")
- print(entry_data)
- return False
- print("DATE:===>", date)
- date = datetime.datetime(*(date[:6]))
- title = "%s: %s" % (feed.name, entry_data.get('title', 'Sin título'))
- content = entry_data.get('content', None)
- if content:
- content = content[0].value
- if not content:
- content = entry_data.get('description', None)
- if not content:
- content = entry_data.get('summary', 'Sin contenido')
- guid = str(entry_data.get('guid', entry_data.link))
- link = entry_data.link
- print(repr([date, title]))
- e = list(Entry.select().where(Entry.guid == guid))
- print(
- repr(dict(
- date=date,
- title=title,
- content=content,
- guid=guid,
- feed=feed,
- link=link,
- ))
- )
- if not e:
- entry = Entry.create(
- date=date,
- title=title,
- content=content,
- guid=guid,
- feed=feed,
- link=link,
- )
- else:
- entry = e[0]
- entry.date = date
- entry.title = title
- entry.content = content
- entry.link = link
- entry.save()
- flag = False
- for feed in Feed.select():
- flag = True
- task = {
- 'basename': self.name + "_fetch_feed",
- 'name': str(feed.url),
- 'actions': [(update_feed, (feed, ))],
- 'uptodate': [timeout(datetime.timedelta(minutes=
- self.site.config.get('PLANETOID_REFRESH', 60)))],
- }
- yield task
- if not flag:
- yield {
- 'basename': self.name + "_fetch_feed",
- 'name': '',
- 'actions': [],
- }
-
- def task_generate_posts(self):
- """Generate post files for the blog entries."""
- def gen_id(entry):
- h = hashlib.md5()
- h.update(entry.feed.name.encode('utf8'))
- h.update(entry.guid)
- return h.hexdigest()
-
- def generate_post(entry):
- unique_id = gen_id(entry)
- meta_path = os.path.join('posts', unique_id + '.meta')
- post_path = os.path.join('posts', unique_id + '.txt')
- with codecs.open(meta_path, 'wb+', 'utf8') as fd:
- fd.write('%s\n' % entry.title.replace('\n', ' '))
- fd.write('%s\n' % unique_id)
- fd.write('%s\n' % entry.date.strftime('%Y/%m/%d %H:%M'))
- fd.write('\n')
- fd.write('%s\n' % entry.link)
- with codecs.open(post_path, 'wb+', 'utf8') as fd:
- fd.write('.. raw:: html\n\n')
- content = entry.content
- if not content:
- content = 'Sin contenido'
- for line in content.splitlines():
- fd.write(' %s\n' % line)
-
- if not os.path.isdir('posts'):
- os.mkdir('posts')
- flag = False
- for entry in Entry.select().order_by(Entry.date.desc()):
- flag = True
- entry_id = gen_id(entry)
- yield {
- 'basename': self.name + "_generate_posts",
- 'targets': [os.path.join('posts', entry_id + '.meta'), os.path.join('posts', entry_id + '.txt')],
- 'name': entry_id,
- 'actions': [(generate_post, (entry,))],
- 'uptodate': [config_changed({1: entry})],
- 'task_dep': [self.name + "_fetch_feed"],
- }
- if not flag:
- yield {
- 'basename': self.name + "_generate_posts",
- 'name': '',
- 'actions': [],
- }
diff --git a/nikola/plugins/command_serve.plugin b/nikola/plugins/command_serve.plugin
deleted file mode 100644
index 684935d..0000000
--- a/nikola/plugins/command_serve.plugin
+++ /dev/null
@@ -1,10 +0,0 @@
-[Core]
-Name = serve
-Module = command_serve
-
-[Documentation]
-Author = Roberto Alsina
-Version = 0.1
-Website = http://nikola.ralsina.com.ar
-Description = Start test server.
-
diff --git a/nikola/plugins/command_serve.py b/nikola/plugins/command_serve.py
deleted file mode 100644
index 64efe7d..0000000
--- a/nikola/plugins/command_serve.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright (c) 2012 Roberto Alsina y otros.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import print_function
-import os
-try:
- from BaseHTTPServer import HTTPServer
- from SimpleHTTPServer import SimpleHTTPRequestHandler
-except ImportError:
- from http.server import HTTPServer # NOQA
- from http.server import SimpleHTTPRequestHandler # NOQA
-
-from nikola.plugin_categories import Command
-
-
-class CommandBuild(Command):
- """Start test server."""
-
- name = "serve"
- doc_usage = "[options]"
- doc_purpose = "Start the test webserver."
-
- cmd_options = (
- {
- 'name': 'port',
- 'short': 'p',
- 'long': 'port',
- 'default': 8000,
- 'type': int,
- 'help': 'Port nummber (default: 8000)',
- },
- {
- 'name': 'address',
- 'short': 'a',
- 'long': '--address',
- 'type': str,
- 'default': '127.0.0.1',
- 'help': 'Address to bind (default: 127.0.0.1)',
- },
- )
-
- def _execute(self, options, args):
- """Start test server."""
- out_dir = self.site.config['OUTPUT_FOLDER']
- if not os.path.isdir(out_dir):
- print("Error: Missing '{0}' folder?".format(out_dir))
- else:
- os.chdir(out_dir)
- httpd = HTTPServer((options['address'], options['port']),
- OurHTTPRequestHandler)
- sa = httpd.socket.getsockname()
- print("Serving HTTP on", sa[0], "port", sa[1], "...")
- httpd.serve_forever()
-
-
-class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
- extensions_map = dict(SimpleHTTPRequestHandler.extensions_map)
- extensions_map[""] = "text/plain"
diff --git a/nikola/plugins/compile/__init__.py b/nikola/plugins/compile/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/nikola/plugins/compile/asciidoc.plugin b/nikola/plugins/compile/asciidoc.plugin
new file mode 100644
index 0000000..47c5608
--- /dev/null
+++ b/nikola/plugins/compile/asciidoc.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = asciidoc
+Module = asciidoc
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Compile ASCIIDoc into HTML
+
diff --git a/nikola/plugins/compile/asciidoc.py b/nikola/plugins/compile/asciidoc.py
new file mode 100644
index 0000000..67dfe1a
--- /dev/null
+++ b/nikola/plugins/compile/asciidoc.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""Implementation of compile_html based on asciidoc.
+
+You will need, of course, to install asciidoc
+
+"""
+
+import codecs
+import os
+import subprocess
+
+from nikola.plugin_categories import PageCompiler
+from nikola.utils import makedirs, req_missing
+
+
+class CompileAsciiDoc(PageCompiler):
+ """Compile asciidoc into HTML."""
+
+ name = "asciidoc"
+
+ def compile_html(self, source, dest, is_two_file=True):
+ makedirs(os.path.dirname(dest))
+ try:
+ subprocess.check_call(('asciidoc', '-f', 'html', '-s', '-o', dest, source))
+ except OSError as e:
+ if e.strreror == 'No such file or directory':
+ req_missing(['asciidoc'], 'build this site (compile with asciidoc)', python=False)
+
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
+ makedirs(os.path.dirname(path))
+ with codecs.open(path, "wb+", "utf8") as fd:
+ if onefile:
+ fd.write("/////////////////////////////////////////////\n")
+ for k, v in metadata.items():
+ fd.write('.. {0}: {1}\n'.format(k, v))
+ fd.write("/////////////////////////////////////////////\n")
+ fd.write("\nWrite your post here.")
diff --git a/nikola/plugins/compile/bbcode.plugin b/nikola/plugins/compile/bbcode.plugin
new file mode 100644
index 0000000..b3d9357
--- /dev/null
+++ b/nikola/plugins/compile/bbcode.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = bbcode
+Module = bbcode
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Compile BBCode into HTML
+
diff --git a/nikola/plugins/compile/bbcode.py b/nikola/plugins/compile/bbcode.py
new file mode 100644
index 0000000..e998417
--- /dev/null
+++ b/nikola/plugins/compile/bbcode.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""Implementation of compile_html based on bbcode."""
+
+import codecs
+import os
+import re
+
+try:
+ import bbcode
+except ImportError:
+ bbcode = None # NOQA
+
+from nikola.plugin_categories import PageCompiler
+from nikola.utils import makedirs, req_missing
+
+
+class CompileBbcode(PageCompiler):
+ """Compile bbcode into HTML."""
+
+ name = "bbcode"
+
+ def __init__(self):
+ if bbcode is None:
+ return
+ self.parser = bbcode.Parser()
+ self.parser.add_simple_formatter("note", "")
+
+ def compile_html(self, source, dest, is_two_file=True):
+ if bbcode is None:
+ req_missing(['bbcode'], 'build this site (compile BBCode)')
+ makedirs(os.path.dirname(dest))
+ with codecs.open(dest, "w+", "utf8") as out_file:
+ with codecs.open(source, "r", "utf8") as in_file:
+ data = in_file.read()
+ if not is_two_file:
+ data = re.split('(\n\n|\r\n\r\n)', data, maxsplit=1)[-1]
+ output = self.parser.format(data)
+ out_file.write(output)
+
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
+ makedirs(os.path.dirname(path))
+ with codecs.open(path, "wb+", "utf8") as fd:
+ if onefile:
+ fd.write('[note][/note]\n\n')
+ fd.write("Write your post here.")
diff --git a/nikola/plugins/compile/html.plugin b/nikola/plugins/compile/html.plugin
new file mode 100644
index 0000000..21dd338
--- /dev/null
+++ b/nikola/plugins/compile/html.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = html
+Module = html
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Compile HTML into HTML (just copy)
+
diff --git a/nikola/plugins/compile/html.py b/nikola/plugins/compile/html.py
new file mode 100644
index 0000000..a309960
--- /dev/null
+++ b/nikola/plugins/compile/html.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""Implementation of compile_html for HTML source files."""
+
+import os
+import shutil
+import codecs
+
+from nikola.plugin_categories import PageCompiler
+from nikola.utils import makedirs
+
+
+class CompileHtml(PageCompiler):
+ """Compile HTML into HTML."""
+
+ name = "html"
+
+ def compile_html(self, source, dest, is_two_file=True):
+ makedirs(os.path.dirname(dest))
+ shutil.copyfile(source, dest)
+ return True
+
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
+ makedirs(os.path.dirname(path))
+ with codecs.open(path, "wb+", "utf8") as fd:
+ if onefile:
+ fd.write('\n\n')
+ fd.write("\nWrite your post here.
")
diff --git a/nikola/plugins/compile/ipynb.plugin b/nikola/plugins/compile/ipynb.plugin
new file mode 100644
index 0000000..3d15bb0
--- /dev/null
+++ b/nikola/plugins/compile/ipynb.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = ipynb
+Module = ipynb
+
+[Documentation]
+Author = Damián Avila
+Version = 1.0
+Website = http://www.oquanta.info
+Description = Compile IPython notebooks into HTML
+
diff --git a/nikola/plugins/compile/ipynb/README.txt b/nikola/plugins/compile/ipynb/README.txt
new file mode 100644
index 0000000..0a7d6db
--- /dev/null
+++ b/nikola/plugins/compile/ipynb/README.txt
@@ -0,0 +1,44 @@
+To make this work...
+
+1- You can install the "jinja-site-ipython" theme using this command:
+
+$ nikola install_theme -n jinja-site-ipython
+
+(or xkcd-site-ipython, if you want xkcd styling)
+
+More info here about themes:
+http://getnikola.com/handbook.html#getting-more-themes
+
+OR
+
+You can to download the "jinja-site-ipython" theme from here:
+https://github.com/damianavila/jinja-site-ipython-theme-for-Nikola
+and copy the "site-ipython" folder inside the "themes" folder of your site.
+
+
+2- Then, just add:
+
+post_pages = (
+ ("posts/*.ipynb", "posts", "post.tmpl", True),
+ ("stories/*.ipynb", "stories", "story.tmpl", False),
+)
+
+and
+
+THEME = 'jinja-site-ipython' (or 'xkcd-site-ipython', if you want xkcd styling)
+
+to your conf.py.
+Finally... to use it:
+
+$nikola new_page -f ipynb
+
+**NOTE**: Just IGNORE the "-1" and "-2" options in nikola new_page command, by default this compiler
+create one metadata file and the corresponding naive IPython notebook.
+
+$nikola build
+
+And deploy the output folder... to see it locally: $nikola serve
+If you have any doubts, just ask: @damianavila
+
+Cheers.
+Damián
diff --git a/nikola/plugins/compile/ipynb/__init__.py b/nikola/plugins/compile/ipynb/__init__.py
new file mode 100644
index 0000000..7c318ca
--- /dev/null
+++ b/nikola/plugins/compile/ipynb/__init__.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2013 Damian Avila.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""Implementation of compile_html based on nbconvert."""
+
+from __future__ import unicode_literals, print_function
+import codecs
+import os
+
+try:
+ from IPython.nbconvert.exporters import HTMLExporter
+ from IPython.nbformat import current as nbformat
+ from IPython.config import Config
+ flag = True
+except ImportError:
+ flag = None
+
+from nikola.plugin_categories import PageCompiler
+from nikola.utils import makedirs, req_missing
+
+
+class CompileIPynb(PageCompiler):
+ """Compile IPynb into HTML."""
+
+ name = "ipynb"
+
+ def compile_html(self, source, dest, is_two_file=True):
+ if flag is None:
+ req_missing(['ipython>=1.0.0'], 'build this site (compile ipynb)')
+ makedirs(os.path.dirname(dest))
+ HTMLExporter.default_template = 'basic'
+ c = Config(self.site.config['IPYNB_CONFIG'])
+ exportHtml = HTMLExporter(config=c)
+ with codecs.open(dest, "w+", "utf8") as out_file:
+ with codecs.open(source, "r", "utf8") as in_file:
+ nb = in_file.read()
+ nb_json = nbformat.reads_json(nb)
+ (body, resources) = exportHtml.from_notebook_node(nb_json)
+ out_file.write(body)
+
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
+ d_name = os.path.dirname(path)
+ makedirs(os.path.dirname(path))
+ meta_path = os.path.join(d_name, kw['slug'] + ".meta")
+ with codecs.open(meta_path, "wb+", "utf8") as fd:
+ if onefile:
+ fd.write('%s\n' % kw['title'])
+ fd.write('%s\n' % kw['slug'])
+ fd.write('%s\n' % kw['date'])
+ fd.write('%s\n' % kw['tags'])
+ print("Your post's metadata is at: ", meta_path)
+ with codecs.open(path, "wb+", "utf8") as fd:
+ fd.write("""{
+ "metadata": {
+ "name": ""
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+ {
+ "cells": [
+ {
+ "cell_type": "code",
+ "collapsed": false,
+ "input": [],
+ "language": "python",
+ "metadata": {},
+ "outputs": []
+ }
+ ],
+ "metadata": {}
+ }
+ ]
+}""")
diff --git a/nikola/plugins/compile/markdown.plugin b/nikola/plugins/compile/markdown.plugin
new file mode 100644
index 0000000..157579a
--- /dev/null
+++ b/nikola/plugins/compile/markdown.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = markdown
+Module = markdown
+
+[Documentation]
+Author = Roberto Alsina
+Version = 0.1
+Website = http://getnikola.com
+Description = Compile Markdown into HTML
+
diff --git a/nikola/plugins/compile/markdown/__init__.py b/nikola/plugins/compile/markdown/__init__.py
new file mode 100644
index 0000000..b41c6b5
--- /dev/null
+++ b/nikola/plugins/compile/markdown/__init__.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2013 Roberto Alsina and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""Implementation of compile_html based on markdown."""
+
+from __future__ import unicode_literals
+
+import codecs
+import os
+import re
+
+try:
+ from markdown import markdown
+
+ from nikola.plugins.compile.markdown.mdx_nikola import NikolaExtension
+ nikola_extension = NikolaExtension()
+
+ from nikola.plugins.compile.markdown.mdx_gist import GistExtension
+ gist_extension = GistExtension()
+
+ from nikola.plugins.compile.markdown.mdx_podcast import PodcastExtension
+ podcast_extension = PodcastExtension()
+
+except ImportError:
+ markdown = None # NOQA
+ nikola_extension = None
+ gist_extension = None
+ podcast_extension = None
+
+from nikola.plugin_categories import PageCompiler
+from nikola.utils import makedirs, req_missing
+
+
+class CompileMarkdown(PageCompiler):
+ """Compile markdown into HTML."""
+
+ name = "markdown"
+ extensions = [gist_extension, nikola_extension, podcast_extension]
+ site = None
+
+ def compile_html(self, source, dest, is_two_file=True):
+ if markdown is None:
+ req_missing(['markdown'], 'build this site (compile Markdown)')
+ makedirs(os.path.dirname(dest))
+ self.extensions += self.site.config.get("MARKDOWN_EXTENSIONS")
+ with codecs.open(dest, "w+", "utf8") as out_file:
+ with codecs.open(source, "r", "utf8") as in_file:
+ data = in_file.read()
+ if not is_two_file:
+ data = re.split('(\n\n|\r\n\r\n)', data, maxsplit=1)[-1]
+ output = markdown(data, self.extensions)
+ out_file.write(output)
+
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
+ makedirs(os.path.dirname(path))
+ with codecs.open(path, "wb+", "utf8") as fd:
+ if onefile:
+ fd.write('\n\n')
+ fd.write("Write your post here.")
diff --git a/nikola/plugins/compile/markdown/mdx_gist.py b/nikola/plugins/compile/markdown/mdx_gist.py
new file mode 100644
index 0000000..3c3bef9
--- /dev/null
+++ b/nikola/plugins/compile/markdown/mdx_gist.py
@@ -0,0 +1,241 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2013 Michael Rabbitt.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Warning: URL formats of "raw" gists are undocummented and subject to change.
+# See also: http://developer.github.com/v3/gists/
+#
+# Inspired by "[Python] reStructuredText GitHub Gist directive"
+# (https://gist.github.com/brianhsu/1407759), public domain by Brian Hsu
+'''
+Extension to Python Markdown for Embedded Gists (gist.github.com)
+
+Basic Example:
+
+ >>> import markdown
+ >>> text = """
+ ... Text of the gist:
+ ... [:gist: 4747847]
+ ... """
+ >>> html = markdown.markdown(text, [GistExtension()])
+ >>> print(html)
+ Text of the gist:
+
+
+
+Example with filename:
+
+ >>> import markdown
+ >>> text = """
+ ... Text of the gist:
+ ... [:gist: 4747847 zen.py]
+ ... """
+ >>> html = markdown.markdown(text, [GistExtension()])
+ >>> print(html)
+ Text of the gist:
+
+
+
+Example using reStructuredText syntax:
+
+ >>> import markdown
+ >>> text = """
+ ... Text of the gist:
+ ... .. gist:: 4747847 zen.py
+ ... """
+ >>> html = markdown.markdown(text, [GistExtension()])
+ >>> print(html)
+ Text of the gist:
+
+
+
+Error Case: non-existent Gist ID:
+
+ >>> import markdown
+ >>> text = """
+ ... Text of the gist:
+ ... [:gist: 0]
+ ... """
+ >>> html = markdown.markdown(text, [GistExtension()])
+ >>> print(html)
+ Text of the gist:
+
+
+
+
+
+
+Error Case: non-existent file:
+
+ >>> import markdown
+ >>> text = """
+ ... Text of the gist:
+ ... [:gist: 4747847 doesntexist.py]
+ ... """
+ >>> html = markdown.markdown(text, [GistExtension()])
+ >>> print(html)
+ Text of the gist:
+
+
+
+
+
+
+'''
+from __future__ import unicode_literals, print_function
+from markdown.extensions import Extension
+from markdown.inlinepatterns import Pattern
+from markdown.util import AtomicString
+from markdown.util import etree
+from nikola.utils import get_logger, req_missing, STDERR_HANDLER
+
+LOGGER = get_logger('compile_markdown.mdx_gist', STDERR_HANDLER)
+
+try:
+ import requests
+except ImportError:
+ requests = None # NOQA
+
+GIST_JS_URL = "https://gist.github.com/{0}.js"
+GIST_FILE_JS_URL = "https://gist.github.com/{0}.js?file={1}"
+GIST_RAW_URL = "https://gist.github.com/raw/{0}"
+GIST_FILE_RAW_URL = "https://gist.github.com/raw/{0}/{1}"
+
+GIST_MD_RE = r'\[:gist:\s*(?P\d+)(?:\s*(?P.+?))?\s*\]'
+GIST_RST_RE = r'(?m)^\.\.\s*gist::\s*(?P\d+)(?:\s*(?P.+))\s*$'
+
+
+class GistFetchException(Exception):
+ '''Raised when attempt to fetch content of a Gist from github.com fails.'''
+ def __init__(self, url, status_code):
+ Exception.__init__(self)
+ self.message = 'Received a {0} response from Gist URL: {1}'.format(
+ status_code, url)
+
+
+class GistPattern(Pattern):
+ """ InlinePattern for footnote markers in a document's body text. """
+
+ def __init__(self, pattern, configs):
+ Pattern.__init__(self, pattern)
+
+ def get_raw_gist_with_filename(self, gist_id, filename):
+ url = GIST_FILE_RAW_URL.format(gist_id, filename)
+ resp = requests.get(url)
+
+ if not resp.ok:
+ raise GistFetchException(url, resp.status_code)
+
+ return resp.text
+
+ def get_raw_gist(self, gist_id):
+ url = GIST_RAW_URL.format(gist_id)
+ resp = requests.get(url)
+
+ if not resp.ok:
+ raise GistFetchException(url, resp.status_code)
+
+ return resp.text
+
+ def handleMatch(self, m):
+ gist_id = m.group('gist_id')
+ gist_file = m.group('filename')
+
+ gist_elem = etree.Element('div')
+ gist_elem.set('class', 'gist')
+ script_elem = etree.SubElement(gist_elem, 'script')
+
+ if requests:
+ noscript_elem = etree.SubElement(gist_elem, 'noscript')
+
+ try:
+ if gist_file:
+ script_elem.set('src', GIST_FILE_JS_URL.format(
+ gist_id, gist_file))
+ raw_gist = (self.get_raw_gist_with_filename(
+ gist_id, gist_file))
+
+ else:
+ script_elem.set('src', GIST_JS_URL.format(
+ gist_id))
+ raw_gist = (self.get_raw_gist(gist_id))
+
+ # Insert source as within