Skip to content

Commit

Permalink
Initial implementation of the content containers system.
Browse files Browse the repository at this point in the history
  • Loading branch information
uhnomoli committed Jul 2, 2013
1 parent f468fed commit b9a2efb
Show file tree
Hide file tree
Showing 24 changed files with 654 additions and 396 deletions.
12 changes: 11 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@

### 0.3 (unreleased)

_In development._
+ __New__
+ A mechanism for defining content containers.
+ Two new filters for the Jinja renderer, `items` and `values`, that act as syntactic sugar for `iteritems()` and `itervalues()`.
+ __Changed__
+ Global and local dictionaries no longer behave different from ordinary Python dictionaries when iterating over them.
+ The `archives` and `tags` globals are now properties of the `posts` global to maintain consistency between content containers.
+ The `post` local has been renamed `item` to maintain consistency between content containers.
+ The `<title>` replacement in URL formats has been renamed to `<slug>`.
+ Frontmatter attributes that contain string values can now be used in URL formats.
+ The `markup` and `parser` config settings have been removed.
+ Which parser used is now determined by _(in order of precedence)_ the `parser` frontmatter attribute, the `parser` container config setting, and lastly the filename.


### 0.2.3 (June 13th, 2013)
Expand Down
2 changes: 1 addition & 1 deletion mynt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
from __future__ import unicode_literals


__version__ = '0.2.3'
__version__ = '0.3-dev'
17 changes: 10 additions & 7 deletions mynt/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@


class Parser(object):
def __init__(self, options):
self.options = options
accepts = ()


def __init__(self, options = None):
self.options = options if options is not None else {}

self.setup()

Expand All @@ -17,21 +20,21 @@ def setup(self):
pass

class Renderer(object):
def __init__(self, path, options, globals_ = {}):
def __init__(self, path, options = None, globals_ = None):
self.path = path
self.options = options
self.globals = globals_
self.options = options if options is not None else {}
self.globals = globals_ if globals_ is not None else {}

self.setup()


def from_string(self, source, vars_ = {}):
def from_string(self, string, data = None):
raise NotImplementedError('A renderer must implement from_string.')

def register(self, key, value):
raise NotImplementedError('A renderer must implement register.')

def render(self, template, vars_ = {}):
def render(self, template, data = None):
raise NotImplementedError('A renderer must implement render.')

def setup(self):
Expand Down
184 changes: 147 additions & 37 deletions mynt/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

from __future__ import unicode_literals

from collections import OrderedDict
from datetime import datetime
import re

import yaml

from mynt.exceptions import ConfigException, PostException
from mynt.fs import File
from mynt.utils import get_logger
from mynt.exceptions import ConfigException
from mynt.fs import Directory
from mynt.utils import absurl, Data, format_url, get_logger, normpath, slugify


yaml.add_constructor('tag:yaml.org,2002:str', lambda loader, node: loader.construct_scalar(node))
Expand All @@ -30,47 +30,157 @@ def __init__(self, string):

pass

class Page(File):
pass

class Post(object):
def __init__(self, post):
self.path = post.path
self.root = post.root
self.name = post.name
self.extension = post.extension
class Container(object):
def __init__(self, name, src, config):
self._pages = None

logger.debug('.. %s%s', self.name, self.extension)
self.name = name
self.src = src
self.path = Directory(normpath(self.src.path, '_containers', self.name))
self.config = config
self.data = Data([], OrderedDict(), OrderedDict())


def _archive(self, container, archive):
for item in container:
year, month = datetime.utcfromtimestamp(item['timestamp']).strftime('%Y %B').decode('utf-8').split()

if year not in archive:
archive[year] = {
'months': OrderedDict({month: [item]}),
'url': self._get_page_url(self.config['archives_url'], year),
'year': year
}
elif month not in archive[year]['months']:
archive[year]['months'][month] = [item]
else:
archive[year]['months'][month].append(item)

def _get_page_url(self, url, text):
slug = slugify(text)

try:
date, self.slug = re.match(r'(\d{4}(?:-\d{2}-\d{2}){1,2})-(.+)', self.name).groups()
self.date = self._get_date(post.mtime, date)
except (AttributeError, ValueError):
raise PostException('Invalid post filename.', 'src: {0}'.format(self.path), 'must be of the format \'YYYY-MM-DD[-HH-MM]-Post-title.md\'')
return format_url(absurl(url, slug), url.endswith('/'))

def _get_pages(self):
pages = []

try:
frontmatter, self.bodymatter = re.search(r'\A---\s+^(.+?)$\s+---\s*(.*)\Z', post.content, re.M | re.S).groups()
except AttributeError:
raise PostException('Invalid post format.', 'src: {0}'.format(self.path), 'frontmatter must not be empty')
for item in self.container:
pages.append((item['layout'], {'item': item}, item['url']))

try:
self.frontmatter = Config(frontmatter)
except ConfigException as e:
raise ConfigException('Invalid post frontmatter.', 'src: {0}'.format(self.path), e.message.lower().replace('.', ''))
if self.config['archive_layout'] and self.archives:
for archive in self.archives.itervalues():
pages.append((
self.config['archive_layout'],
{'archive': archive},
archive['url']
))

if self.config['tag_layout'] and self.tags:
for tag in self.tags.itervalues():
pages.append((
self.config['tag_layout'],
{'tag': tag},
tag['url']
))

return pages

def _sort(self, container, key, reverse = False):
def sort(item):
attribute = item.get(key, item)

if isinstance(attribute, basestring):
return attribute.lower()

return attribute

container.sort(key = sort, reverse = reverse)


def add(self, item):
self.container.append(item)

def archive(self):
self._archive(self.container, self.archives)

if 'layout' not in self.frontmatter:
raise PostException('Invalid post frontmatter.', 'src: {0}'.format(self.path), 'layout must be set')
for tag in self.tags.itervalues():
self._archive(tag['container'], tag['archives'])

def sort(self):
self._sort(self.container, self.config['sort'], self.config['reverse'])

def _get_date(self, mtime, date):
d = [None, None, None, 0, 0]
def tag(self):
tags = []

for i, v in enumerate(date.split('-')):
d[i] = v
for item in self.container:
item['tags'].sort(key = unicode.lower)

for tag in item['tags']:
if tag not in self.tags:
self.tags[tag] = []

self.tags[tag].append(item)

for name, container in self.tags.iteritems():
tags.append({
'archives': OrderedDict(),
'count': len(container),
'name': name,
'container': container,
'url': self._get_page_url(self.config['tags_url'], name)
})

self._sort(tags, 'name')
self._sort(tags, 'count', True)

self.tags.clear()

for tag in tags:
self.tags[tag['name']] = tag


@property
def archives(self):
return self.data.archives

@property
def container(self):
return self.data.container

@property
def pages(self):
if self._pages is None:
self._pages = self._get_pages()

return self._pages

@property
def tags(self):
return self.data.tags


class Posts(Container):
def __init__(self, src, config):
super(Posts, self).__init__('posts', src, config)

self.path = Directory(normpath(self.src.path, '_posts'))

self._update_config()


def _update_config(self):
config = {
'archives_url': 'archives_url',
'archive_layout': 'archive_layout',
'reverse': True,
'sort': 'timestamp',
'tags_url': 'tags_url',
'tag_layout': 'tag_layout',
'url': 'posts_url'
}

if not d[3]:
d[3], d[4] = mtime.strftime('%H %M').decode('utf-8').split()
elif not d[4]:
d[4] = '{0:02d}'.format(d[4])
for k, v in config.iteritems():
config[k] = self.config.get(v, v)

return datetime.strptime('-'.join(d), '%Y-%m-%d-%H-%M')
self.config = config
Loading

0 comments on commit b9a2efb

Please sign in to comment.