I would like to implement similar functionality to Django template's custom
>> tags in Jinja.
>>
>> In Django templates, to have a server-side function return a string
>>
> The example you give returns a dictionary. Not a string.


> and have it inserted to a template, you can do something  as this:
>>
>> from django import template
>>
>> register = template.Library()
>> @register.inclusion_tag('app/gallery.html')def get_gallery(active=None):
>>     ### do complex server side things, etc.
>>
>>     context_dict = {
>>         ...
>>     }
>>     return context_dict
>>
>> You are updating the context of the template or only returning a variable
that ca be used with a /for/ and /with/ tags?


> Now, calling this is as simple as
>>
>> {% load app_tags %}{% get_gallery 'home' %}
>>
>> This seems clear enough for me.
>>
> See my above question please.

> Now my question: even after reading through all parts of the Jinja2
>> documentation, I do not see how a similar functionality should be
>> implemented in Jinja. I've seen that Jinja supports macros, calls, imports,
>> custom tests, custom filters, but I cannot see anything related to defining
>> custom tags with arbitrary server-side Python code.
>>
> The main feature is that you can not replace context variable function
with filters or the otherway around :))) #CognitveLoad

Two concepts I know of:

- filters, like /{live|love}/ this one will turn the content of the
variable /live/ to something computed in python function named in the
context /love/. filters can not be used as context variables.

- context function that returns a context or string or else; maybe the
context is updated. Anyway you can use assignements
<http://jinja.pocoo.org/docs/dev/templates/#assignments> instead of
django's with.


> The closest I've seen is custom filters, but that has a weird syntax when
>> used for tag-like purposes: to call tag(param1, param2), you'd need to
>> write param1|tag(param2). Also, I don't see how it can be used for
>> parameter-less functions.
>>
> not if you put the function in the context just like /user/, /message/,
pass procedure as a variable of the template context.


> There is also the whole extension support, but that seems overly
>> complicated with the simplest example
>> <http://jinja.pocoo.org/docs/dev/extensions/#example-extension> taking
>> up 56 lines of code in the doc.
>>
>> Can you explain how do you solve this problem, both in standalone Jinja2
>> environment (think static site generation)
>>
> I attached the make.py of my blog. There is a function called "jinja" it's
commented what i does. and I also attach the index.jinja and base.jinja
they must live in ./_templates. I never use this engine to do jinja alone.
If you can't make it work hit me. Here is the jinja code:

```
  *-{{ "007"|love(42) }}*
  {% for ognoin in live(1985, 12, 31, bof="0x12ee7df") %}
    [[{{ ognoin }}]]
    {{ live(132)|love(4096) }}
  {% endfor %}

  {# {% for fish in love(1337) %}  #}
  {#   {{ "off" }}  #}
  {# {% endfor %}  #}
```
rendering is at: http://www.hyperdev.fr/#main

The context is populated by jinja function:

```
def jinja(template, **context):
    """Render template /template/ found in /.//_templates// directory.

    wrapper around /Jinja.render/ where do add filter or improve context."""
    path = str(Path('./_templates').resolve())

    # to be used as a filter and context function
    def love(abc, *args, **kwargs):
        return ['a', 'b', 'c', 'abc', abc, args, kwargs]

    return Jinja.render(
        # the relative path of the directory we want
        # to render to, relative to /path/.
        template,
        # the root path where to look for templates
        # both the above /template/ but also other
        # template references in templates.
        path,
        # filter are text transformers. Some kind of html tags.
        # use functions in context to create Djano-like tags
        # aka. tags that be both manipulated the conte
        filters=dict(love=love),
        # every keyword except filters are made
        # available in the template context. The keyword
        # contexte variable is dubbed with the key of the
        # keyword argument.
        # Here /love/ is stored as /live/.
        live=love,
        # what was passed as context is forwarded as-is
        **context
    )
```

Jinja.render is a *generic* frontend to jinja that allows to only use one
class. You init with base settings and then use this jinja instance
(usually several times). Here jinja **/function/** is only a shortcut *in
the context  of /make.py/*.

Anyway, /love/ is a filter, /live/ is a function variable. /live/ does
everything you need.

and under the new Django versions (1.8+)?
>>
> Not time for this this time..

NB: look up the code I render epub and pdf ^_^

>  --
> You received this message because you are subscribed to the Google Groups
> "pocoo-libs" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> To post to this group, send email to [email protected].
> Visit this group at http://groups.google.com/group/pocoo-libs.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"pocoo-libs" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/pocoo-libs.
For more options, visit https://groups.google.com/d/optout.
#!/usr/bin/env python3
# Copyright 2014 Amirouche Boubekki <[email protected]>
# This work is free. You can redistribute it and/or modify it under the
# terms of the Frak It To Public License, Version 14.08 or later.
#
#
#                    FRAK IT TO PUBLIC LICENSE
#                          Version 14.08

# Copyright (C) 2014 Amirouche Boubekki <[email protected]>

# Everyone is permitted to copy and distribute verbatim or modified
# copies of this license document, and changing it is allowed as long
# as the name is changed.

#                    FRAK IT TO PUBLIC LICENSE
#   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

#  0. Do what the FRAK you can do.
#  1. Do FRAK what the FRAK you can do
#  3. Do FRAK what the FRAK you can FRAK do
import os
import re

from shlex import split as split_command
from time import altzone
from datetime import datetime
from datetime import timezone
from datetime import timedelta
from subprocess import call
from subprocess import check_output
from pathlib import Path

from docopt import docopt
from slugify import slugify
from feedgen.feed import FeedGenerator
from ebooklib import epub

from azf import HTML
from azf import Jinja


class NoteBuilder(HTML):

    def image(self, url, text=''):
        # make text optional & absolutize urls
        with self._inline():
            url = ''.join(self.to_html(url))
        url = self._context['baseurl'] + '/' + url
        return '<div class="image"><img src="%s" /></div>' % url

    def href(self, text, url):
        # XXX: invert url and text to match html
        return super().href(url, text)

    def keyword(self, value):
        with self._inline():
            name = ''.join(self.to_html(value)).strip()
        try:
            self._context['keywords'].append(name)
        except KeyError:
            self._context['keywords'] = [name]
        return ''

    def title(self, value):
        with self._inline():
            title = ''.join(self.to_html(value))
        self._context['title'] = title
        self._context['slug'] = slug = slugify(title)
        return '<h1 id="%s">%s</h1>' % (slug, title)

    def introduction(self, value):
        intro = ''.join(self.to_html(value))
        self._context['introduction'] = intro
        return intro

    def bold(self, value):
        with self._inline():
            value = ''.join(self.to_html(value))
        return '<b>%s</b>' % value

    def emphase(self, value):
        with self._inline():
            value = ''.join(self.to_html(value))
        return '<emphase>%s</emphase>' % value

    def context(self, name, value=None):
        if value:
            name, value = map(lambda x: ''.join(self.to_html(x)), (name, value))
            self._context[name] = value
            return ''
        else:
            return super().context(name)


def new_draft(title):
    title = title.capitalize()
    slug = slugify(title)
    directory = os.path.join('_drafts', slug)
    if os.path.exists(directory):
        print('Ark! There is already a draft with this name.')
        exit(1)
    else:
        os.makedirs(directory)
        index = os.path.join(directory, 'index.azf')
        with open(index, 'w') as f:
            body = 'âµ£title{%s}\n\n' % title
            f.write(body)
    print('Done. You can edit %s' % index)


__settings__ = None

def settings():
    global __settings__
    if not __settings__:
        import settings
        __settings__ = dict()
        for key, value in vars(settings).items():
            if key.isupper():
                __settings__[key] = value
    return __settings__

    
def jinja(template, **context):
    context.update(settings())
    path = str(Path('./_templates').resolve())
    return Jinja.render(template, path, **context)


def keywordize(rooturl, keyword):
    slug = slugify(keyword)
    return dict(
        name=keyword,
        slug=keyword,
        url='%s/keyword/%s' % (rooturl, slug),
    )


def build_a_note(rooturl, relativedir):
    print('* building', relativedir)
    # parse azf file
    absdir = relativedir.resolve()
    filepath = absdir / 'index.azf'
    baseurl = rooturl + '/' + str(relativedir)
    with filepath.open() as f:
        context = NoteBuilder.render(f.read(), str(absdir), rooturl=rooturl, baseurl=baseurl)

    # improve context
    context['url'] = '%s/%s' % (rooturl, str(relativedir))
    context['keywords'] = list(map(lambda keyword: keywordize(rooturl, keyword), sorted(context['keywords'])))

    created_at = re.findall('(\d+)/(\d+)/(\d+)', str(relativedir))[0]
    created_at = map(int, created_at)
    created_at = datetime(*list(created_at))
    context['created_at'] = created_at

    context['url'] = rooturl + '/' + str(relativedir)
    context['slug'] = slugify(context['title'])

    # render jinja
    output = jinja('note.jinja2', **context)
    index = relativedir / 'index.html'
    with index.open('w') as f:
        f.write(output)

    return context


def atom(title, alternate, self, path, notes):
    feed = FeedGenerator()
    feed.author({
        'name': 'Amirouche Boubekki',
        'email': '[email protected]'
    })
    feed.id(alternate)
    feed.title(title)
    feed.link(href=alternate, rel='alternate')
    feed.link(href=self, rel='self')
    for note in notes:
        entry = feed.add_entry()
        entry.id(note['url'])
        entry.title(note['title'])
        entry.link(href=note['url'])
        created_at = note['created_at']
        year, month, day = (created_at.year, created_at.month, created_at.day)
        delta = -timedelta(seconds=altzone)
        delta = timezone(delta)
        published = datetime(year, month, day, tzinfo=delta)
        entry.published(published)
    feed.atom_file(path)


def minify(filepath):
    if filepath.endswith('jquery.js'):
        print('* ignored', filepath)
        return
    if filepath.endswith('css'):
        command = 'cleancss '
    elif filepath.endswith('js'):
        command = 'uglifyjs'
    elif filepath.endswith('html'):
        command = 'html-minifier --conservative-collapse  --collapse-whitespace'
    else:
        print('* ignored', filepath)
        return
    print('* minify', filepath)
    command = command + ' ' + filepath
    command = split_command(command)
    output = check_output(command)
    with open(filepath, 'wb') as w:
        w.write(output)


def autoprefix_dir(path):
    basecommand = 'autoprefixer --browsers "last 3 version" %s'
    for root, dirs, files in os.walk(path):
        for filename in files:
            filepath = os.path.join(root, filename)
            if filename.endswith('css'):
                command = basecommand % filepath
                command = split_command(command)
                call(command)


def rebuild(rooturl, dev=False):
    curdir = Path('.')

    # rerender all notes and return them
    print('building notes')
    paths = curdir.glob('notes/*/*/*/*/')
    notes = list(map(lambda path: build_a_note(rooturl, path), paths))

    notes.sort(key=lambda x: x['created_at'], reverse=True)

    # build keywords pages
    print('building keywords')
    keywords = dict()
    for note in notes:
        for keyword in note['keywords']:
            try:
                keywords[keyword['name']]['notes'].append(note)
            except:
                keywords[keyword['name']] = dict(keyword)
                keywords[keyword['name']]['notes'] = [note]

    keywords = sorted(keywords.values(), key=lambda x: x['name'])

    # build keyword pages with feeds
    for keyword in keywords:
        slug = keyword['slug']
        path = curdir / 'keyword' / slug

        print('* building', str(path))

        try:
            path.mkdir(parents=True)
        except OSError:
            pass

        output = jinja(
            'keyword.jinja2',
            keyword=keyword,
        )

        index = path / 'index.html'
        with index.open('w') as f:
            f.write(output)

        title = 'hypermove.net @ %s' % keyword['name']
        atompath = str(path / 'atom.xml')
        atom(title, keyword['url'], keyword['url'] + 'atom.xml', atompath, keyword['notes'])

    print('building index')
    output = jinja(
        'index.jinja2',
        notes=notes,
        keywords=keywords,
        rooturl=rooturl,
    )

    index = curdir / 'index.html'

    with index.open('w') as f:
        f.write(output)

    print('building 404 page')
    output = jinja('404.jinja2', rooturl=rooturl)

    with open('404.html', 'w') as f:
        f.write(output)

    print('rendering feed')
    title = '[email protected]'
    atompath = str(curdir / 'atom.xml')
    atom(title, rooturl, rooturl + 'atom.xml', atompath, notes)

    print('building single page html')
    output = jinja('print.jinja2', notes=notes)

    with open('hypermove.net.html', 'w') as f:
        f.write(output)

    # print('autoprefix & minify all the thing')
    # list(map(lambda x: minify(str(x)), curdir.resolve().glob('notes/**/index.html')))
    # list(map(lambda x: minify(str(x)), curdir.resolve().glob('static/*')))
    # minify(str(curdir.resolve() / 'index.html'))
    # autoprefix_dir(str(curdir.resolve() / 'static'))

    print('building epub')
    # https://github.com/aerkalov/ebooklib
    book = epub.EpubBook()
    book.set_title('hypermove.net')
    book.add_author('Amirouche Boubekki')
    spine = ['nav']
    # create chapter
    for note in notes:
        title = note['title']
        filename = note['slug'] + '.xhtml'
        c = epub.EpubHtml(
            title=title,
            file_name=filename,
        )
        c.content = note['body']
        spine.append(c)
        book.add_item(c)
        book.toc.append(epub.Link(filename, title, note['slug']))
    book.add_item(epub.EpubNcx())
    book.add_item(epub.EpubNav())

    book.spine = spine
    epub.write_epub('hypermove.net.epub', book, {})

    if not dev:
        print('building pdf')
        command = 'weasyprint hypermove.net.html hypermove.net.pdf -s static/print.css'
        command = command.split()
        call(command)




def main():
    doc = """main.py.

Usage:
  main.py draft <title>
  main.py publish <path>
  main.py build
  main.py rebuild [--dev]
  main.py -h | --help
  main.py --version

Options:
  -h --help               Show this screen.
  --version               Show version.
"""
    arguments = docopt(doc, version='15.02.13')
    if os.environ.get('DEBUG', False):
        print(arguments)
    # dispatch
    if arguments['draft']:
        title = arguments['<title>']
        new_draft(title)
        print('Done!')
    elif arguments['publish']:
        path = arguments['<path>']
        slug = path.split('/')[-1]
        if not slug:
            slug = path.split('/')[-2]
        created_at = datetime.now()
        created_at = created_at.strftime('%Y/%m/%d')
        target = os.path.join('notes', created_at, slug)
        # move(path, target)
        print('Done!')
    elif arguments['rebuild']:
        if arguments['--dev']:
            rebuild('http://127.0.0.1:8001', dev=True)
        else:
            rebuild('http://www.hyperdev.io')
        print('Rebuild done!')
    else:
        message = 'Oops! What did happen here?! Please fill '
        message += 'a bug report with a lot of details using DEBUG=1 '
        message += 'and send output at [email protected]'
        message += ', thanks!'
        raise Exception(message)


if __name__ == '__main__':
    main()

Attachment: index.jinja2
Description: Binary data

Attachment: base.jinja2
Description: Binary data

Reply via email to