<commit_before># coding=utf-8
import logging
import markdown
import re
import times
import yaml
from codecs import open
from datetime import datetime
from dateutil import parser
from flufl.enum._enum import Enum
from path import path
from typogrify.templatetags.jinja2_filters import typogrify
from zope.cachedescriptors.property import CachedProperty
from engineer.conf import settings
from engineer.exceptions import PostMetadataError
from engineer.filters import localtime
from engineer.util import slugify, chunk, urljoin

try:
    import cPickle as pickle
except ImportError:
    import pickle

__author__ = '<EMAIL>'

logger = logging.getLogger(__name__)

class Status(Enum):
    """Enum representing the status of a :class:`~Post`."""
    draft = 0 #: Post is a draft.
    published = 1 #: Post is published.
    review = 2 #: Post is in review.

    def __reduce__(self):
        return 'Status'


class Post(object):
    """
    Represents a post written in Markdown and stored in a file.

    :param source: path to the source file for the post.
    """
    _regex = re.compile(r'^[\n|\r\n]*(?P<metadata>.+?)[\n|\r\n]*---[\n|\r\n]*(?P<content>.*)[\n|\r\n]*', re.DOTALL)

    def __init__(self, source):
        self.source = path(source).abspath()
        """The absolute path to the source file for the post."""

        self.html_template_path = 'theme/post_detail.html'
        """The path to the template to use to transform the post into HTML."""

        self.markdown_template_path = 'core/post.md'
        """The path to the template to use to transform the post back into a :ref:`post source file <posts>`."""

        metadata, self._content_raw = self._parse_source()

        self.title = metadata.pop('title', self.source.namebase.replace('-', ' ').replace('_', ' '))
        """The title of the post."""

        self.slug = metadata.pop('slug', slugify(self.title))
        """The slug for the post."""

        self.tags = metadata.pop('tags', [])
        """A list of strings representing the tags applied to the post."""

        self.link = metadata.pop('link', None)
        """The post's :ref:`external link <post link>`."""

        self.via = metadata.pop('via', None)
        """The post's attribution name."""

        self.via_link = metadata.pop('via-link', metadata.pop('via_link', None))
        """The post's attribution link."""

        try:
            self.status = Status(metadata.pop('status', Status.draft.name))
            """The status of the post (published or draft)."""

        except ValueError:
            logger.warning("'%s': Invalid status value in metadata. Defaulting to 'draft'." % self.title)
            self.status = Status.draft

        self.timestamp = metadata.pop('timestamp', None)<commit_msg>Add support for specifying a single tag without using the YAML list format.<commit_after># coding=utf-8
import logging
import markdown
import re
import times
import yaml
from codecs import open
from datetime import datetime
from dateutil import parser
from flufl.enum._enum import Enum
from path import path
from typogrify.templatetags.jinja2_filters import typogrify
from zope.cachedescriptors.property import CachedProperty
from engineer.conf import settings
from engineer.exceptions import PostMetadataError
from engineer.filters import localtime
from engineer.util import slugify, chunk, urljoin, wrap_list

try:
    import cPickle as pickle
except ImportError:
    import pickle

__author__ = '<EMAIL>'

logger = logging.getLogger(__name__)

class Status(Enum):
    """Enum representing the status of a :class:`~Post`."""
    draft = 0 #: Post is a draft.
    published = 1 #: Post is published.
    review = 2 #: Post is in review.

    def __reduce__(self):
        return 'Status'


class Post(object):
    """
    Represents a post written in Markdown and stored in a file.

    :param source: path to the source file for the post.
    """
    _regex = re.compile(r'^[\n|\r\n]*(?P<metadata>.+?)[\n|\r\n]*---[\n|\r\n]*(?P<content>.*)[\n|\r\n]*', re.DOTALL)

    def __init__(self, source):
        self.source = path(source).abspath()
        """The absolute path to the source file for the post."""

        self.html_template_path = 'theme/post_detail.html'
        """The path to the template to use to transform the post into HTML."""

        self.markdown_template_path = 'core/post.md'
        """The path to the template to use to transform the post back into a :ref:`post source file <posts>`."""

        metadata, self._content_raw = self._parse_source()

        self.title = metadata.pop('title', self.source.namebase.replace('-', ' ').replace('_', ' '))
        """The title of the post."""

        self.slug = metadata.pop('slug', slugify(self.title))
        """The slug for the post."""

        self.tags = wrap_list(metadata.pop('tags', []))
        """A list of strings representing the tags applied to the post."""

        self.link = metadata.pop('link', None)
        """The post's :ref:`external link <post link>`."""

        self.via = metadata.pop('via', None)
        """The post's attribution name."""

        self.via_link = metadata.pop('via-link', metadata.pop('via_link', None))
        """The post's attribution link."""

        try:
            self.status = Status(metadata.pop('status', Status.draft.name))
            """The status of the post (published or draft)."""

        except ValueError:
            logger.warning("'%s': Invalid status value in metadata. Defaulting to 'draft'." % self.title)
            self.status = Status.draft

        self.timestamp = metadata.pop('timestamp', None)
{'fraction_non_alphanumeric': 0.13038831492696829, 'fraction_numerical': 0.0017812611328820805, 'mean_word_length': 3.0541516245487363, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '721532', 'n_tokens_mistral': 1638, 'n_tokens_neox': 1574, 'n_words': 538}