From 8b14a1e5b2ca574fdd4fd2377567ec98a110d4b6 Mon Sep 17 00:00:00 2001 From: Agustin Henze Date: Wed, 13 Mar 2013 20:58:39 -0300 Subject: Imported Upstream version 5.4.2 --- nikola/plugins/__init__.py | 1 - nikola/plugins/command_bootswatch_theme.py | 57 +++-- nikola/plugins/command_build.plugin | 10 - nikola/plugins/command_build.py | 67 ------ nikola/plugins/command_check.py | 67 ++++-- nikola/plugins/command_console.py | 32 ++- nikola/plugins/command_deploy.py | 8 +- nikola/plugins/command_import_blogger.py | 139 ++++++------ nikola/plugins/command_import_wordpress.py | 240 +++++++++++++-------- nikola/plugins/command_init.py | 44 ++-- nikola/plugins/command_install_theme.py | 57 ++--- nikola/plugins/command_new_post.py | 127 +++++++---- nikola/plugins/command_serve.py | 38 ++-- nikola/plugins/compile_bbcode.py | 8 +- nikola/plugins/compile_html.py | 8 +- nikola/plugins/compile_markdown/__init__.py | 14 +- nikola/plugins/compile_rest/__init__.py | 10 +- nikola/plugins/compile_rest/gist_directive.py | 12 +- .../compile_rest/pygments_code_block_directive.py | 39 ++-- nikola/plugins/compile_rest/slides.py | 7 +- nikola/plugins/compile_rest/soundcloud.py | 32 +++ nikola/plugins/compile_rest/vimeo.py | 10 +- nikola/plugins/compile_rest/youtube.py | 8 +- nikola/plugins/compile_textile.py | 8 +- nikola/plugins/compile_txt2tags.py | 8 +- nikola/plugins/task_create_bundles.py | 12 +- nikola/plugins/task_indexes.py | 4 +- nikola/plugins/task_redirect.py | 5 +- nikola/plugins/task_render_galleries.py | 11 +- nikola/plugins/task_render_listings.py | 56 +++-- nikola/plugins/task_render_rss.py | 4 +- nikola/plugins/task_render_tags.py | 22 +- nikola/plugins/task_sitemap/__init__.py | 26 ++- nikola/plugins/task_sitemap/sitemap_gen.py | 2 +- 34 files changed, 685 insertions(+), 508 deletions(-) delete mode 100644 nikola/plugins/command_build.plugin delete mode 100644 nikola/plugins/command_build.py create mode 100644 nikola/plugins/compile_rest/soundcloud.py (limited to 'nikola/plugins') diff --git a/nikola/plugins/__init__.py b/nikola/plugins/__init__.py index 2d9b1b2..b1de7f1 100644 --- a/nikola/plugins/__init__.py +++ b/nikola/plugins/__init__.py @@ -1,4 +1,3 @@ from __future__ import absolute_import from . import command_import_wordpress # NOQA -from . import command_build # NOQA diff --git a/nikola/plugins/command_bootswatch_theme.py b/nikola/plugins/command_bootswatch_theme.py index 6c1061f..8400c9f 100644 --- a/nikola/plugins/command_bootswatch_theme.py +++ b/nikola/plugins/command_bootswatch_theme.py @@ -23,7 +23,6 @@ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. from __future__ import print_function -from optparse import OptionParser import os try: @@ -38,37 +37,53 @@ 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 run(self, *args): + 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 - parser = OptionParser(usage="nikola %s [options]" % self.name) - parser.add_option("-n", "--name", dest="name", - help="New theme name (default: custom)", - default='custom') - parser.add_option("-s", "--swatch", dest="swatch", - help="Name of the swatch from bootswatch.com " - "(default: slate)", default='slate') - parser.add_option("-p", "--parent", dest="parent", - help="Parent theme name (default: site)", - default='site') - (options, args) = parser.parse_args(list(args)) - name = options.name - swatch = options.swatch - parent = options.parent + name = options['name'] + swatch = options['swatch'] + parent = options['parent'] - print("Creating '%s' theme from '%s' and '%s'" % ( - name, swatch, 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 = 'http://bootswatch.com/%s/%s' % (swatch, fname) + 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), @@ -77,5 +92,5 @@ class CommandBootswatchTheme(Command): with open(os.path.join('themes', name, 'parent'), 'wb+') as output: output.write(parent) - print('Theme created. Change the THEME setting to "%s" to use it.' % - name) + print('Theme created. Change the THEME setting to "{0}" to use ' + 'it.'.format(name)) diff --git a/nikola/plugins/command_build.plugin b/nikola/plugins/command_build.plugin deleted file mode 100644 index 7d029a7..0000000 --- a/nikola/plugins/command_build.plugin +++ /dev/null @@ -1,10 +0,0 @@ -[Core] -Name = build -Module = command_build - -[Documentation] -Author = Roberto Alsina -Version = 0.1 -Website = http://nikola.ralsina.com.ar -Description = Build the site. - diff --git a/nikola/plugins/command_build.py b/nikola/plugins/command_build.py deleted file mode 100644 index 8a1de5f..0000000 --- a/nikola/plugins/command_build.py +++ /dev/null @@ -1,67 +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 -import os -import tempfile - -from nikola.plugin_categories import Command - - -class CommandBuild(Command): - """Build the site.""" - - name = "build" - - def run(self, *args): - """Build the site using doit.""" - - # FIXME: this is crap, do it right - with tempfile.NamedTemporaryFile(suffix='.py', delete=False) as self.dodo: - self.dodo.write(b''' -import sys -sys.path.insert(0, '.') -from doit.reporter import ExecutedOnlyReporter -DOIT_CONFIG = { - 'reporter': ExecutedOnlyReporter, - 'default_tasks': ['render_site'], -} -from nikola import Nikola -import conf -SITE = Nikola(**conf.__dict__) - - -def task_render_site(): - return SITE.gen_tasks() - ''') - self.dodo.flush() - first = args[0] if args else None - if first in ('auto', 'clean', 'forget', 'ignore', 'list', 'run'): - cmd = first - args = args[1:] - else: - cmd = 'run' - os.system('doit %s -f %s -d . %s' % (cmd, self.dodo.name, - ''.join(args))) - os.unlink(self.dodo.name) diff --git a/nikola/plugins/command_check.py b/nikola/plugins/command_check.py index ae19c41..a396f63 100644 --- a/nikola/plugins/command_check.py +++ b/nikola/plugins/command_check.py @@ -23,9 +23,7 @@ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. from __future__ import print_function -from optparse import OptionParser import os -import sys try: from urllib import unquote from urlparse import urlparse @@ -42,25 +40,48 @@ class CommandCheck(Command): name = "check" - def run(self, *args): + 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.""" - parser = OptionParser(usage="nikola %s [options]" % self.name) - parser.add_option('-l', '--check-links', dest='links', - action='store_true', - help='Check for dangling links.') - parser.add_option('-f', '--check-files', dest='files', - action='store_true', help='Check for unknown files.') - - (options, args) = parser.parse_args(list(args)) - if options.links: - scan_links() - if options.files: + if not options['links'] and not options['files']: + print(self.help()) + return False + if options['links']: + scan_links(options['find_sources']) + if options['files']: scan_files() existing_targets = set([]) -def analize(task): +def analize(task, find_sources=False): try: filename = task.split(":")[-1] d = lxml.html.fromstring(open(filename).read()) @@ -79,26 +100,26 @@ def analize(task): if os.path.exists(target_filename): existing_targets.add(target_filename) else: - print("In %s broken link: " % filename, target) - if '--find-sources' in sys.argv: + print("Broken link in {0}: ".format(filename), target) + if find_sources: print("Possible sources:") - print(os.popen( - 'nikola build list --deps %s' % task, 'r').read()) + print(os.popen('nikola list --deps ' + task, + 'r').read()) print("===============================\n") except Exception as exc: print("Error with:", filename, exc) -def scan_links(): +def scan_links(find_sources=False): print("Checking Links:\n===============\n") - for task in os.popen('nikola build list --all', 'r').readlines(): + 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: - analize(task) + analize(task, find_sources) def scan_files(): @@ -106,7 +127,7 @@ def scan_files(): task_fnames = set([]) real_fnames = set([]) # First check that all targets are generated in the right places - for task in os.popen('nikola build list --all', 'r').readlines(): + for task in os.popen('nikola list --all', 'r').readlines(): task = task.strip() if 'output' in task and ':' in task: fname = task.split(':')[-1] diff --git a/nikola/plugins/command_console.py b/nikola/plugins/command_console.py index 7a009fd..4af759f 100644 --- a/nikola/plugins/command_console.py +++ b/nikola/plugins/command_console.py @@ -22,6 +22,8 @@ # 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 @@ -31,5 +33,31 @@ class Deploy(Command): """Start debugging console.""" name = "console" - def run(self, *args): - os.system('python -i -c "from nikola.console import *"') + def _execute(self, options, args): + """Start the console.""" + from nikola import Nikola + try: + import conf + SITE = Nikola(**conf.__dict__) + SITE.scan_posts() + print("You can now access your configuration as conf and your " + "site engine as SITE.") + except ImportError: + print("No configuration found.") + import code + try: + import readline + except ImportError: + pass + else: + import rlcompleter + readline.set_completer(rlcompleter.Completer(globals()).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=globals()) diff --git a/nikola/plugins/command_deploy.py b/nikola/plugins/command_deploy.py index 48d6e91..ffa86ab 100644 --- a/nikola/plugins/command_deploy.py +++ b/nikola/plugins/command_deploy.py @@ -23,7 +23,6 @@ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. from __future__ import print_function -from optparse import OptionParser import os from nikola.plugin_categories import Command @@ -33,9 +32,10 @@ class Deploy(Command): """Deploy site. """ name = "deploy" - def run(self, *args): - parser = OptionParser(usage="nikola %s [options]" % self.name) - (options, args) = parser.parse_args(list(args)) + doc_usage = "" + doc_purpose = "Deploy the site." + + def _execute(self, command, args): for command in self.site.config['DEPLOY_COMMANDS']: print("==>", command) os.system(command) diff --git a/nikola/plugins/command_import_blogger.py b/nikola/plugins/command_import_blogger.py index aea210a..35a702e 100644 --- a/nikola/plugins/command_import_blogger.py +++ b/nikola/plugins/command_import_blogger.py @@ -27,7 +27,6 @@ import codecs import csv import datetime import os -from optparse import OptionParser import time try: @@ -52,9 +51,63 @@ 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 Wordpress 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 @@ -65,7 +118,7 @@ class CommandImportBlogger(Command): src = (urlparse(k).path + 'index.html')[1:] dst = (urlparse(v).path) if src == 'index.html': - print("Can't do a redirect for: %r" % k) + print("Can't do a redirect for: {0!r}".format(k)) else: redirections.append((src, dst)) @@ -73,11 +126,11 @@ class CommandImportBlogger(Command): def generate_base_site(self): if not os.path.exists(self.output_folder): - os.system('nikola init --empty %s' % (self.output_folder, )) + os.system('nikola init --empty ' + self.output_folder) else: self.import_into_existing_site = True - print('The folder %s already exists - assuming that this is a ' - 'already existing nikola site.' % self.output_folder) + 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')) @@ -92,7 +145,7 @@ class CommandImportBlogger(Command): context['BLOG_TITLE'] = channel.feed.title context['BLOG_DESCRIPTION'] = '' # Missing in the dump - context['BLOG_URL'] = channel.feed.link.rstrip('/') + 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'] = '''( @@ -124,12 +177,8 @@ class CommandImportBlogger(Command): @staticmethod def write_metadata(filename, title, slug, post_date, description, tags): with codecs.open(filename, "w+", "utf8") as fd: - fd.write('%s\n' % title) - fd.write('%s\n' % slug) - fd.write('%s\n' % post_date) - fd.write('%s\n' % ','.join(tags)) - fd.write('\n') - fd.write('%s\n' % description) + fd.write('\n'.join((title, slug, post_date, ','.join(tags), '', + description))) def import_item(self, item, out_folder=None): """Takes an item from the feed and creates a post file.""" @@ -145,8 +194,8 @@ class CommandImportBlogger(Command): # blogger supports empty titles, which Nikola doesn't if not title: - print("Warning: Empty title in post with URL %s. Using NO_TITLE " - "as placeholder, please fix." % link) + 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'): @@ -179,11 +228,11 @@ class CommandImportBlogger(Command): else: is_draft = False - self.url_map[link] = self.context['BLOG_URL'] + '/' + \ + self.url_map[link] = self.context['SITE_URL'] + '/' + \ out_folder + '/' + slug + '.html' if is_draft and self.exclude_drafts: - print('Draft "%s" will not be imported.' % (title, )) + 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) @@ -195,8 +244,8 @@ class CommandImportBlogger(Command): os.path.join(self.output_folder, out_folder, slug + '.html'), content) else: - print('Not going to import "%s" because it seems to contain' - ' no content.' % (title, )) + 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 @@ -235,10 +284,10 @@ class CommandImportBlogger(Command): if not self.import_into_existing_site: filename = 'conf.py' else: - filename = 'conf.py.wordpress_import-%s' % datetime.datetime.now( - ).strftime('%Y%m%d_%H%M%s') + 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: %s' % config_output_path) + print('Configuration will be written to: ' + config_output_path) return config_output_path @@ -247,54 +296,6 @@ class CommandImportBlogger(Command): with codecs.open(filename, 'w+', 'utf8') as fd: fd.write(rendered_template) - def run(self, *arguments): - """Import a Wordpress 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 - - parser = OptionParser( - usage="nikola %s [options] blogger_export_file" % self.name) - parser.add_option('-f', '--filename', dest='filename', - help='Blogger export file from which the import is ' - 'made.') - parser.add_option('-o', '--output-folder', dest='output_folder', - default='new_site', - help='The location into which the imported content ' - 'will be written') - parser.add_option('-d', '--no-drafts', dest='exclude_drafts', - default=False, action="store_true", help='Do not ' - 'import drafts.') - - (options, args) = parser.parse_args(list(arguments)) - - if not options.filename and args: - options.filename = args[0] - - if not options.filename: - parser.print_usage() - return - - 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)) - def replacer(dst): return links.get(dst, dst) diff --git a/nikola/plugins/command_import_wordpress.py b/nikola/plugins/command_import_wordpress.py index 07028d8..e7ecca0 100644 --- a/nikola/plugins/command_import_wordpress.py +++ b/nikola/plugins/command_import_wordpress.py @@ -28,7 +28,6 @@ import csv import datetime import os import re -from optparse import OptionParser try: from urlparse import urlparse @@ -53,9 +52,104 @@ 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 + print(options, args) + if requests is None: + print('To use the import_wordpress command,' + ' you have to install the "requests" package.') + return - @staticmethod - def read_xml_file(filename): + if not args: + print(self.help()) + return + + options['filename'] = args[0] + + if len(args) > 1: + options['output_folder'] = args[1] + + 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: @@ -64,9 +158,8 @@ class CommandImportWordpress(Command): if b' %s" % (url, dst_path)) + print("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[link] = '/' + dst_url @@ -173,10 +271,18 @@ class CommandImportWordpress(Command): return new_caption - @classmethod - def transform_content(cls, content): - new_content = cls.transform_sourcecode(content) - return cls.transform_caption(new_content) + 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): @@ -188,13 +294,16 @@ class CommandImportWordpress(Command): @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('%s\n' % title) - fd.write('%s\n' % slug) - fd.write('%s\n' % post_date) - fd.write('%s\n' % ','.join(tags)) + 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('%s\n' % description) + 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.""" @@ -208,19 +317,19 @@ class CommandImportWordpress(Command): slug = utils.slugify(urlparse(link).path) if not slug: # it happens if the post has no "nice" URL slug = get_text_tag( - item, '{%s}post_name' % wordpress_namespace, None) + item, '{{{0}}}post_name'.format(wordpress_namespace), None) if not slug: # it *may* happen slug = get_text_tag( - item, '{%s}post_id' % wordpress_namespace, None) + 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, '{%s}post_date' % wordpress_namespace, None) + item, '{{{0}}}post_date'.format(wordpress_namespace), None) status = get_text_tag( - item, '{%s}status' % wordpress_namespace, 'publish') + item, '{{{0}}}status'.format(wordpress_namespace), 'publish') content = get_text_tag( item, '{http://purl.org/rss/1.0/modules/content/}encoded', '') @@ -237,13 +346,13 @@ class CommandImportWordpress(Command): continue tags.append(text) - self.url_map[link] = self.context['BLOG_URL'] + '/' + \ - out_folder + '/' + slug + '.html' - if is_draft and self.exclude_drafts: - print('Draft "%s" will not be imported.' % (title, )) + 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, @@ -253,15 +362,15 @@ class CommandImportWordpress(Command): os.path.join(self.output_folder, out_folder, slug + '.wp'), content) else: - print('Not going to import "%s" because it seems to contain' - ' no content.' % (title, )) + 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, '{%s}post_type' % wordpress_namespace, 'post') + item, '{{{0}}}post_type'.format(wordpress_namespace), 'post') if post_type == 'attachment': self.import_attachment(item, wordpress_namespace) @@ -285,10 +394,10 @@ class CommandImportWordpress(Command): if not self.import_into_existing_site: filename = 'conf.py' else: - filename = 'conf.py.wordpress_import-%s' % datetime.datetime.now( - ).strftime('%Y%m%d_%H%M%s') + 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: %s' % config_output_path) + print('Configuration will be written to:', config_output_path) return config_output_path @@ -297,53 +406,6 @@ class CommandImportWordpress(Command): with codecs.open(filename, 'w+', 'utf8') as fd: fd.write(rendered_template) - def run(self, *arguments): - """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 - - parser = OptionParser(usage="nikola %s [options] " - "wordpress_export_file" % self.name) - parser.add_option('-f', '--filename', dest='filename', - help='WordPress export file from which the import ' - 'made.') - parser.add_option('-o', '--output-folder', dest='output_folder', - default='new_site', help='The location into which ' - 'the imported content will be written') - parser.add_option('-d', '--no-drafts', dest='exclude_drafts', - default=False, action="store_true", help='Do not ' - 'import drafts.') - - (options, args) = parser.parse_args(list(arguments)) - - if not options.filename and args: - options.filename = args[0] - - if not options.filename: - parser.print_usage() - return - - self.wordpress_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.wordpress_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)) - def replacer(dst): return links.get(dst, dst) diff --git a/nikola/plugins/command_init.py b/nikola/plugins/command_init.py index e9bd001..bc36266 100644 --- a/nikola/plugins/command_init.py +++ b/nikola/plugins/command_init.py @@ -23,7 +23,6 @@ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. from __future__ import print_function -from optparse import OptionParser, OptionGroup import os import shutil import codecs @@ -39,16 +38,23 @@ class CommandInit(Command): name = "init" - usage = """Usage: nikola init folder [options]. - -That will create a sample site in the specified folder. -The destination folder must not exist. -""" + 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", - 'BLOG_URL': "http://nikola.ralsina.com.ar", + 'SITE_URL': "http://nikola.ralsina.com.ar", 'BLOG_EMAIL': "joe@demo.site", 'BLOG_DESCRIPTION': "This is a demo site for Nikola.", 'DEFAULT_LANG': "en", @@ -67,7 +73,7 @@ The destination folder must not exist. "wiki": ('.wiki',), "ipynb": ('.ipynb',), "html": ('.html', '.htm') - }""", +}""", 'REDIRECTIONS': '[]', } @@ -95,32 +101,22 @@ The destination folder must not exist. def get_path_to_nikola_modules(): return os.path.dirname(nikola.__file__) - def run(self, *args): + def _execute(self, options={}, args=None): """Create a new site.""" - parser = OptionParser(usage=self.usage) - group = OptionGroup(parser, "Site Options") - group.add_option( - "--empty", action="store_true", dest='empty', default=True, - help="Create an empty site with only a config.") - group.add_option("--demo", action="store_false", dest='empty', - help="Create a site filled with example data.") - parser.add_option_group(group) - (options, args) = parser.parse_args(list(args)) - if not args: print("Usage: nikola init folder [options]") - return + return False target = args[0] if target is None: print(self.usage) else: - if options.empty: + if not options or not options.get('demo'): self.create_empty_site(target) - print('Created empty site at %s.' % 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 %s." - % 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.py b/nikola/plugins/command_install_theme.py index 0dc000b..04a2cce 100644 --- a/nikola/plugins/command_install_theme.py +++ b/nikola/plugins/command_install_theme.py @@ -23,7 +23,6 @@ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. from __future__ import print_function -from optparse import OptionParser import os import json from io import BytesIO @@ -41,31 +40,39 @@ 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 run(self, *args): + def _execute(self, options, args): """Install theme into current site.""" - if requests is None: - print('To use the install_theme command, you need to install the ' - '"requests" package.') - return - parser = OptionParser(usage="nikola %s [options]" % self.name) - parser.add_option("-l", "--list", dest="list", action="store_true", - help="Show list of available themes.") - parser.add_option("-n", "--name", dest="name", help="Theme name", - default=None) - parser.add_option("-u", "--url", dest="url", help="URL for the theme " - "repository" "(default: " - "http://nikola.ralsina.com.ar/themes/index.json)", - default='http://nikola.ralsina.com.ar/themes/' - 'index.json') - (options, args) = parser.parse_args(list(args)) - - listing = options.list - name = options.name - url = options.url + 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 the -n or the -l option.") + print("This command needs either a theme name or the -l option.") return False data = requests.get(url).text data = json.loads(data) @@ -84,11 +91,11 @@ class CommandInstallTheme(Command): os.makedirs("themes") except: raise OSError("mkdir 'theme' error!") - print('Downloading: %s' % data[name]) + print('Downloading: ' + data[name]) zip_file = BytesIO() zip_file.write(requests.get(data[name]).content) - print('Extracting: %s into themes' % name) + print('Extracting: {0} into themes'.format(name)) utils.extract_all(zip_file) else: - print("Can't find theme %s" % name) + print("Can't find theme " + name) return False diff --git a/nikola/plugins/command_new_post.py b/nikola/plugins/command_new_post.py index 9b6397b..a823da3 100644 --- a/nikola/plugins/command_new_post.py +++ b/nikola/plugins/command_new_post.py @@ -25,7 +25,6 @@ from __future__ import unicode_literals, print_function import codecs import datetime -from optparse import OptionParser import os import sys @@ -51,9 +50,9 @@ def filter_post_pages(compiler, is_post, post_compilers, post_pages): if not filtered: type_name = "post" if is_post else "page" raise Exception("Can't find a way, using your configuration, to create" - "a %s in format %s. You may want to tweak " - "post_compilers or post_pages in conf.py" % - (type_name, compiler)) + "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] @@ -61,42 +60,88 @@ class CommandNewPost(Command): """Create a new post.""" name = "new_post" - - def run(self, *args): - """Create a 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': 'rest', + '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")] - parser = OptionParser(usage="nikola %s [options]" % self.name) - parser.add_option('-p', '--page', dest='is_post', action='store_false', - default=True, help='Create a page instead of a blog ' - 'post.') - parser.add_option('-t', '--title', dest='title', help='Title for the ' - 'page/post.', default=None) - parser.add_option('--tags', dest='tags', help='Comma-separated tags ' - 'for the page/post.', default='') - parser.add_option('-1', dest='onefile', action='store_true', - help='Create post with embedded metadata (single ' - 'file format).', - default=self.site.config.get('ONE_FILE_POSTS', True)) - parser.add_option('-2', dest='onefile', action='store_false', - help='Create post with separate metadata (two file ' - 'format).', - default=self.site.config.get('ONE_FILE_POSTS', True)) - parser.add_option('-f', '--format', dest='post_format', default='rest', - help='Format for post (one of %s)' % - ','.join(compiler_names)) - (options, args) = parser.parse_args(list(args)) - - is_post = options.is_post - title = options.title - tags = options.tags - onefile = options.onefile - post_format = options.post_format + 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 post_format not in compiler_names: - print("ERROR: Unknown post format %s" % post_format) + print("ERROR: Unknown post format " + post_format) return compiler_plugin = self.site.plugin_manager.getPluginByName( post_format, "PageCompiler").plugin_object @@ -118,19 +163,29 @@ class CommandNewPost(Command): if isinstance(title, bytes): title = title.decode(sys.stdin.encoding) title = title.strip() - slug = utils.slugify(title) + if not path: + slug = utils.slugify(title) + else: + 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:] - txt_path = os.path.join(output_path, slug + suffix) + 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, slug, date, tags) if not onefile: # write metadata file diff --git a/nikola/plugins/command_serve.py b/nikola/plugins/command_serve.py index 75e07a9..64efe7d 100644 --- a/nikola/plugins/command_serve.py +++ b/nikola/plugins/command_serve.py @@ -23,7 +23,6 @@ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. from __future__ import print_function -from optparse import OptionParser import os try: from BaseHTTPServer import HTTPServer @@ -39,26 +38,39 @@ class CommandBuild(Command): """Start test server.""" name = "serve" + doc_usage = "[options]" + doc_purpose = "Start the test webserver." - def run(self, *args): - """Start test server.""" - - parser = OptionParser(usage="nikola %s [options]" % self.name) - parser.add_option("-p", "--port", dest="port", help="Port numer " - "(default: 8000)", default=8000, type="int") - parser.add_option("-a", "--address", dest="address", help="Address to " - "bind (default: 127.0.0.1)", default='127.0.0.1') - (options, args) = parser.parse_args(list(args)) + 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 '%s' folder?" % out_dir) + print("Error: Missing '{0}' folder?".format(out_dir)) else: os.chdir(out_dir) - httpd = HTTPServer((options.address, options.port), + httpd = HTTPServer((options['address'], options['port']), OurHTTPRequestHandler) sa = httpd.socket.getsockname() - print("Serving HTTP on {0[0]} port {0[1]}...".format(sa)) + print("Serving HTTP on", sa[0], "port", sa[1], "...") httpd.serve_forever() diff --git a/nikola/plugins/compile_bbcode.py b/nikola/plugins/compile_bbcode.py index fd7fe1a..26de727 100644 --- a/nikola/plugins/compile_bbcode.py +++ b/nikola/plugins/compile_bbcode.py @@ -68,10 +68,10 @@ class CompileTextile(PageCompiler): with codecs.open(path, "wb+", "utf8") as fd: if onefile: fd.write('[note][/note]\n\n') diff --git a/nikola/plugins/compile_html.py b/nikola/plugins/compile_html.py index 850a3e5..6c1c381 100644 --- a/nikola/plugins/compile_html.py +++ b/nikola/plugins/compile_html.py @@ -51,10 +51,10 @@ class CompileHtml(PageCompiler): with codecs.open(path, "wb+", "utf8") as fd: if onefile: fd.write('\n\n') diff --git a/nikola/plugins/compile_markdown/__init__.py b/nikola/plugins/compile_markdown/__init__.py index 5eb25c8..7aa03a9 100644 --- a/nikola/plugins/compile_markdown/__init__.py +++ b/nikola/plugins/compile_markdown/__init__.py @@ -55,8 +55,10 @@ class CompileMarkdown(PageCompiler): output = markdown(data, ['fenced_code', 'codehilite']) # h1 is reserved for the title so increment all header levels for n in reversed(range(1, 9)): - output = re.sub('' % n, '' % (n + 1), output) - output = re.sub('' % n, '' % (n + 1), output) + output = re.sub(''.format(n), ''.format(n + 1), + output) + output = re.sub(''.format(n), ''.format(n + 1), + output) # python-markdown's highlighter uses the class 'codehilite' to wrap # code, # instead of the standard 'code'. None of the standard # pygments stylesheets use this class, so swap it to be 'code' @@ -69,10 +71,10 @@ class CompileMarkdown(PageCompiler): with codecs.open(path, "wb+", "utf8") as fd: if onefile: fd.write('\n\n') diff --git a/nikola/plugins/compile_rest/__init__.py b/nikola/plugins/compile_rest/__init__.py index 4191add..b0a0c00 100644 --- a/nikola/plugins/compile_rest/__init__.py +++ b/nikola/plugins/compile_rest/__init__.py @@ -44,6 +44,8 @@ from .slides import slides directives.register_directive('slides', slides) from .gist_directive import GitHubGist directives.register_directive('gist', GitHubGist) +from .soundcloud import soundcloud +directives.register_directive('soundcloud', soundcloud) from nikola.plugin_categories import PageCompiler @@ -75,10 +77,10 @@ class CompileRest(PageCompiler): tags=""): with codecs.open(path, "wb+", "utf8") as fd: if onefile: - fd.write('.. title: %s\n' % title) - fd.write('.. slug: %s\n' % slug) - fd.write('.. date: %s\n' % date) - fd.write('.. tags: %s\n' % tags) + fd.write('.. title: {0}\n'.format(title)) + fd.write('.. slug: {0}\n'.format(slug)) + fd.write('.. date: {0}\n'.format(date)) + fd.write('.. tags: {0}\n'.format(tags)) fd.write('.. link: \n') fd.write('.. description: \n\n') fd.write("\nWrite your post here.") diff --git a/nikola/plugins/compile_rest/gist_directive.py b/nikola/plugins/compile_rest/gist_directive.py index 3bfe818..0ea8f23 100644 --- a/nikola/plugins/compile_rest/gist_directive.py +++ b/nikola/plugins/compile_rest/gist_directive.py @@ -24,11 +24,11 @@ class GitHubGist(Directive): has_content = False def get_raw_gist_with_filename(self, gistID, filename): - url = "https://raw.github.com/gist/%s/%s" % (gistID, filename) + url = '/'.join(("https://raw.github.com/gist", gistID, filename)) return requests.get(url).text def get_raw_gist(self, gistID): - url = "https://raw.github.com/gist/%s/" % (gistID) + url = "https://raw.github.com/gist/{0}/".format(gistID) return requests.get(url).text def run(self): @@ -43,12 +43,12 @@ class GitHubGist(Directive): if 'file' in self.options: filename = self.options['file'] rawGist = (self.get_raw_gist_with_filename(gistID, filename)) - embedHTML = ('') % (gistID, filename) + embedHTML = ('').format(gistID, filename) else: rawGist = (self.get_raw_gist(gistID)) - embedHTML = ('') % gistID + embedHTML = ('').format(gistID) return [nodes.raw('', embedHTML, format='html'), nodes.raw('', '