diff options
Diffstat (limited to 'nikola/plugins/command/new_post.py')
| -rw-r--r-- | nikola/plugins/command/new_post.py | 291 |
1 files changed, 291 insertions, 0 deletions
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) |
