Esempio n. 1
0
    def get_template(self, key):
        """Helper method to retrieve the appropriate template for the given
        key.

        If the node is a type, look first for a template called
        type_[key].html, in ALL the template directories.  If found, load it
        as the template.  If not found, load type.html as the template.

        Look for a template named [key].html in ALL the template directories.
        If found, use it as the template.

        If no template yet, look for a template named [type].html in ALL the
        template directories.  If found, use it as the template.

        If no template yet, use _node.html as the template.

        Note that this logic is not in the template loader, because it resolves
        keys to filenames, and is not applicable during {% extends foo %}.

        """
        node = self.space[key]
        # Mercurial can't handle filenames containing ':' on Windows, so:
        key_filename = re.sub(':', '_', filekey(key))

        def find_template(filename):
            for template_dir in self.template_dirs:
                full_filename = os.path.join(template_dir, filename)
                if os.path.exists(full_filename):
                    return full_filename
            return None

        if node['type'] == 'type':
            type_filename = 'type_' + key_filename
            if find_template(type_filename):
                return self.jinja2_env.get_template(type_filename)
            else:
                return self.jinja2_env.get_template('type.html')

        if find_template(key_filename):
            return self.jinja2_env.get_template(key_filename)

        type_filename = filekey(node['type'])
        if find_template(type_filename):
            return self.jinja2_env.get_template(type_filename)

        return self.jinja2_env.get_template('_node.html')
Esempio n. 2
0
def make_news_feed(universe, data, dir, filename, limit=None):
    """Generate Atom feeds from Article nodes in the given Chrysoberyl
    data.

    """
    url = BASEURL + filename
    try:
        os.makedirs(dir)
    except OSError:
        pass
    filename = os.path.join(dir, filename)
    articles = []
    for key in data:
        node = data[key]
        if node['type'] != 'Article':
            continue
        n = {}
        n.update(node)
        n['key'] = key
        # Note, these are now done differently from how md2html()
        # is done in the templates elsewhere
        for field_name in ('summary', 'description', 'commentary'):
            field = markdown_field(universe, data, node, field_name,
                                   prefix='http://catseye.tc/')
            if field is not None:
                n[field_name + '_html'] = field.encode("ascii",
                                                       "xmlcharrefreplace")
        articles.append(n)

    articles.sort(key=itemgetter('publication-date'), reverse=True)
    entries = []
    for n in articles:
        title = n['key']
        guid = url + "/" + n['key']
        updated = n['publication-date']
        nodelink = 'http://catseye.tc/node/' + pathname2url(filekey(n['key']))
        summary_contents = n['description_html']
        if n.get('summary', None) is not None:
            summary_contents = (n['summary_html'] +
                '<p><a href="%s">Read more...</a></p>' % nodelink)
        summary = atomize.Summary(summary_contents, content_type='html')
        links = [atomize.Link(nodelink, content_type='text/html',
                              rel='alternate')]
        entry = atomize.Entry(title=title, guid=guid, updated=updated,
                              summary=summary, links=links)
        entries.append(entry)

    if limit and len(entries) > limit:
        entries = entries[:limit]

    feed = atomize.Feed(title="Cat's Eye Technologies: New Developments",
                        updated=datetime.datetime.utcnow(),
                        guid=url,
                        author='Chris Pressey',
                        self_link=url,
                        entries=entries)

    feed.write_file(filename)
Esempio n. 3
0
    def render_node(self, key, node):
        """Render the given Chrysoberyl node (with the given key) as an HTML
        document.

        """
        context = node.copy()
        context['key'] = key
        context['sleek_key'] = sleek_key
        context['pathname2url'] = pathname2url

        # Context functions.  Being nested functions of render_node lets
        # them easily access (close over) the current node and its key.

        def expose(fun):
            context[fun.__name__] = fun
            return fun

        @expose
        def basename(path):
            return os.path.basename(path)

        @expose
        def get_node(key=key, space=self.space):
            return self.universe.get_node(key, default_space=space)

        @expose
        def get_space(name=None):
            if name is None:
                return self.space
            return self.universe[name]

        @expose
        def md2html(field_contents, prefix='../', fixed=False):
            if fixed:
                md = '\n'.join(
                    ['    ' + l for l in field_contents.split('\n')]
                )
                return markdown.markdown(md)
            else:
                return markdown_contents(
                    self.universe, field_contents, prefix=prefix,
                    sleek=self.sleek_node_links
                )

        @expose
        def markdown_file_to_html(filename):
            # FIXME makes awful assumptions
            prefix = 'http://catseye.tc/modules/'
            parts = filename.split(prefix)
            if len(parts) == 2:
                filename = os.path.join(self.projection_dir, parts[1])
            with codecs.open(filename, 'r', 'utf-8') as f:
                md = f.read()
            return markdown.markdown(md)

        @expose
        def html_file_to_html(filename):
            # FIXME makes awful assumptions
            prefix = 'http://catseye.tc/modules/'
            parts = filename.split(prefix)
            if len(parts) == 2:
                filename = os.path.join(self.projection_dir, parts[1])
            with codecs.open(filename, 'r', 'utf-8') as f:
                collect = False
                html = []
                for line in f:
                    if line.strip() == '<body>':
                        collect = True
                    elif line.strip() == '</body>':
                        collect = False
                    elif collect:
                        html.append(line)
            return ''.join(html)

        @expose
        def empty(iterable):
            for x in iterable:
                return False
            return True

        @expose
        def count(iterable):
            return len(list(iterable))

        @expose
        def filter_items(predicate):
            """Return a list of (key, node) pairs in the current namespace
            for which the given predicate returns True.

            """
            for nkey, node in self.space.iteritems():
                if predicate(node):
                    yield (nkey, node)

        @expose
        def implementations_by_p(type_, auspice):
            """A predicate for returning the implementations conducted
            under the given auspice.

            """
            def f(node):
                if node.get('hidden', False):
                    return False
                if 'implementation-of' not in node:
                    return False
                cnode = get_node(node['implementation-of'][0])
                if cnode['type'] != type_:
                    return False
                if 'auspices' in cnode and auspice in cnode['auspices']:
                    return False
                if 'auspices' not in node:
                    return False
                if auspice not in node['auspices']:
                    return False
                return True
            return f

        # incomplete and circumstantial.  used on front page only
        @expose
        def ours_p():
            auspices = ("Cat's Eye Technologies", "What is this I don't even",)
            def f(key, node):
                if node.get('hidden', False):
                    return False
                if not is_current(node):
                    return False
                if 'auspices' not in node:
                    return False
                ok = False
                for auspice in auspices:
                    if auspice in node['auspices']:
                        ok = True
                        break
                if not ok:
                    return False
                return True
            return f

        @expose
        def has_online_implementation_p():
            def f(key, node):
                return bool(online_implementations(key))
            return f

        @expose
        def related_items(relationship, key=key, filter=None):
            return self.space.related_items(relationship, key, filter=filter)

        # maybe will be deprecated: use related_items instead
        @expose
        def related(*args, **kwargs):
            for (k, n) in related_items(*args, **kwargs):
                yield k

        @expose
        def group_by(iterable, group_by):
            groups = {}
            for (nkey, node) in iterable:
                if node.get('hidden', False):
                    continue
                memberships = node.get(group_by)
                if not isinstance(memberships, list):
                    memberships = [memberships]
                for membership in memberships:
                    groups.setdefault(membership, []).append(nkey)
            return groups

        @expose
        def iteritems_sorted_within(assoc, sorts):
            for key in sorts:
                if key == 'None':
                    key = None
                if key in assoc:
                    yield (key, assoc[key])
            for (key, stuff) in sorted(assoc.iteritems()):
                if key is not None and key not in sorts:
                    #print "Note: '%s' not in sort keys" % key
                    yield (key, stuff)

        @expose
        def keys_sorted_within(keys, sorts):
            for key in sorts:
                if key == 'None':
                    key = None
                if key in keys:
                    yield key
            for key in sorted(keys):
                if key is not None and key not in sorts:
                    #print "Note: '%s' not in sort keys" % key
                    yield key

        @expose
        def is_current(node):
            if 'development-stage' not in node:
                return True
            return node['development-stage'] not in (
                'idea', 'work in progress', 'abandoned', 'unfinished',
                'archived', 'lost',
            )

        @expose
        def collect_auspices(items):
            """Group nodes with the given type by auspices.  Only include nodes
            which are current, and for the "None" auspices, nodes which have not
            been implemented by 'us'."""
            us = [
                "Cat's Eye Technologies",
                "What is this I don't even",
            ]
            collected_auspices = {}
            for auspices, v in group_by(items, 'auspices').iteritems():
                v = [k for k in v if is_current(get_node(k))]
                if auspices == None:
                    v = [k for k in v if not has_implementation_by_one_of(k, us)]
                if v:
                    collected_auspices[auspices] = v
            return collected_auspices

        @expose
        def has_implementation_by_one_of(key, auspices):
            for (ikey, node) in related_items('implementation-of', key=key):
                iauspices = node.get('auspices', [])
                for auspice in auspices:
                    if auspice in iauspices:
                        return True
            return False

        @expose
        def impls_for_platform(plat_key, key=key):
            for (ikey, node) in related_items('implementation-of', key=key):
                if (node.get('platform', None) == plat_key or
                    node.get('host_platform', None) == plat_key or
                    node.get('target_platform', None) == plat_key):
                    yield ikey

        @expose
        def ref_impl(key=key):
            return self.space.reference_implementation_of(key)

        @expose
        def ref_dist(key=key):
            """Find the reference distribution for the given node, which is
            assumed to be an implementable.

            If a `defining-distribution` is given, then that is
            the reference distribution.

            Otherwise, if there is a reference implementation, and the
            reference implementation is in more than zero distributions,
            the first such distribution is the reference distribution.

            Otherwise None.

            """
            node = self.universe.get_node(key)
            if 'defining-distribution' in node:
                return node['defining-distribution']

            ref_i = ref_impl(key=key)
            if ref_i is not None:
                if 'in-distributions' in self.universe.get_node(ref_i):
                    return self.universe.get_node(ref_i)['in-distributions'][0]

            return None

        @expose
        def documentation(key=key):
            """Return a list of documentation file names for the given key."""
            filenames = []
            node = self.universe.get_node(key)
            if 'github' in node:
                path = os.path.join(
                    self.projection_dir, get_distname(node),
                )
                for filename in find_likely_documents(path):
                    filenames.append(filename)

            return sorted(filenames)

        @expose
        def documentation_link(filename, key=key):
            node = self.universe.get_node(key)
            # This is a URL. TODO don't make so many assumptions in URLs.
            path = os.path.join(
                '..', 'view',
                get_distname(node),
                pathname2url(filename)
            )
            # TODO: stat the file, fallback to Github link if not there
            return path

        @expose
        def github_link(filename, key=key):
            """Return an HTML link to a file in the Github repository
            associated with the given key, which is assumed to be a
            distribution.

            """
            # apparently not used anymore!
            raise NotImplementedError
            return "https://github.com/%s/blob/master/%s" % (
                pathname2url(self.universe.get_node(key)['github']),
                pathname2url(filename)
            )

        @expose
        def indefart(text):
            """Try to return a reasonable indefinite article to precede the
            given noun phrase.

            """
            # "u" is dicey
            if text.startswith(("a", "e", "i", "o", "u")):
                return "an " + text
            else:
                return "a " + text

        @expose
        def plural(text):
            """Try to return a reasonable pluralized version of the
            given noun phrase.

            """
            if text[-4:] == 'dium':
                return text[:-4] + 'dia'
            if text[-5:] == 'maton':
                return text[:-5] + 'mata'
            if text[-1:] == 's':
                return text + 'es'
            if text[-1:] == 'y':
                return text[:-1] + 'ies'
            return text + 's'

        _indefart = indefart
        _plural = plural

        @expose
        def link(key, format="%s", indefart=False, lower=False, plural=False,
                 link_text=None, title=None, extra_attr=''):
            """Return an HTML link to the node with the given key.

            indefart causes the link text to be preceded by an indefinite
            article.  lower causes the link text to be lowercase.
            plural causes the link text to be pluralized.

            If the node is of a type that suppresses page generation, this
            will raise an exception.  In the future, it may intelligently
            divert to a different node.

            """
            if link_text is None:
                (_, ukey, _) = self.universe.get_space_key_node(key)
                link_text = ukey
                if lower:
                    link_text = link_text.lower()
                if indefart:
                    link_text = _indefart(key)
                if plural:
                    link_text = _plural(link_text)
                link_text = format % link_text
            return transformer.link(
                self.universe, key, link_text, title=title,
                extra_attr=extra_attr, sleek=self.sleek_node_links
            )

        # not the kind you're probably thinking of
        @expose
        def linked_list(keys, format="%s"):
            """Given a list of keys, return a fragment of HTML containing
            links to nodes with those keys.  The list is formatted in a human-
            readable fashion, with commas, and the word "and" before the
            end.

            """
            if len(keys) == 1:
                return link(keys[0], format=format)
            elif len(keys) == 2:
                return "%s and %s" % (link(keys[0], format=format),
                                      link(keys[1], format=format))
            else:
                front = keys[:-1]
                last = keys[-1]
                return "%s and %s" % (
                    ', '.join([link(f, format=format) for f in front]),
                    link(last, format=format)
                )

        def online_locations(key):
            l = self.space[key].get('online-locations', [])
            if l:
                return l
            if key.endswith(' (mp3)'):
                return ['installation/' + key[:-6]]
            if key.endswith(' (PNG)'):
                return ['installation/' + key[:-6]]
            if key.endswith(' (GIF)'):
                return ['installation/' + key[:-6]]
            if key.endswith(' (JPEG)'):
                return ['installation/' + key[:-7]]
            return []

        def online_implementations(key):
            online_locs = []
            online_locs.extend(online_locations(key))
            for impl in sorted(related('implementation-of', key=key)):
                online_locs.extend(online_locations(impl))
            return online_locs

        @expose
        def online_buttons(key=key, show_verb_phrase=True):
            html = ''
            for loc_key in sorted(online_implementations(key)):
                mediums = self.universe.get_node(loc_key)['mediums']
                medium = 'Online'
                if 'Java applet' in mediums:
                    medium = '(Java applet)'
                assert 'HTML5' in mediums, 'No good medium in ' + \
                                           ' on '.join(mediums)
                if show_verb_phrase:
                    node = self.universe.get_node(key)
                    if node['type'] == 'Game':
                        link_text = 'Play'
                    elif node['type'] == 'Musical Composition':
                        link_text = 'Listen'
                    elif node['type'] in ('Text', 'Book'):
                        link_text = 'Read it'
                    elif node['type'] in ('Picture',):
                        link_text = 'See it'
                    else:
                        link_text = 'Try it'
                    link_text += ' ' + medium
                else:
                    link_text = medium
                html += link(
                    loc_key, link_text=link_text,
                    extra_attr='class="button" '
                ) + ' '
            return html

        @expose
        def strip_outer_p(text):
            match = re.match(
                r'^\s*\<p\>\s*(.*?)\s*\<\/p\>\s*$', text,
                re.DOTALL
            )
            if match:
                return match.group(1)
            return text

        @expose
        def debug(text):
            print
            print text
            print
            return ''

        @expose
        def error(text):
            raise ValueError("'%s'" % text)

        @expose
        def _assert(cond):
            assert cond
            return ''

        @expose
        def breadcrumbs(key=key):
            """Generate breadcrumbs for the node given by key."""
            # ("This function's more like spaghetti than breadcrumbs,"
            # quips Release Notes Girl.  Captain Compiler responds:
            # "Does that mean we're going to make it open-sauce?  HA, HA")
            TOP = "Chrysoberyl"
            bc = []
            if key != TOP:
                while 'domain' in self.universe.get_node(key):
                    key = self.universe.get_node(key)['domain']
                    if key == TOP:
                        break
                    bc.append(link(key))
                if key != TOP and self.universe.get_node(key)['type'] != 'type':
                    bc.append(link(self.universe.get_node(key)['type'], plural=True))
                bc.append(link(TOP))
            bc.append('<a href="../">catseye.tc</a>')
            bc.reverse()
            return bc

        @expose
        def recommended_implementation(implementable, key=key):
            """Return a node representing the recommended implementation of
            the given key (assumed to be an implementable.)

            """
            node = self.universe.get_node(key)
            if 'recommended-implementation' in node:
                return node['recommended-implementation']
            if empty(related('implementation-of', key=implementable)):
                return None
            impls = [i for i in related('implementation-of', key=implementable)]
            if len(impls) == 1:
                return impls[0]
            candidates = []
            # XXX can we statically check this?
            for impl in impls:
                if 'generally-recommended' in self.universe.get_node(impl):
                    candidates.append(impl)
            if len(candidates) == 1:
                return candidates[0]
            raise KeyError("Implementable %s has more than one generally "
                           "recommended implementation (under key %s)" %
                           (implementable, key))

        @expose
        def lingography():
            """Return a list of all entries that will be shown on the
            lingography.  This is a bespoke context function, rather than
            a Jinja2 template using related(), because we want to display
            a count of the items in the template.

            """
            languages = []
            types = ('Programming Language', 'Programming Language Family',
                     'Conlang', 'Automaton')
            for thing in self.space:
                node = self.space[thing]
                if (node['type'] in types and
                    'Chris Pressey' in node.get('authors', []) and
                    node.get('development-stage', 'idea') not in \
                        ('idea', 'work in progress') and
                    not node.get('variant-of', None) and
                    (node.get('member-of', None) != 'Funge-98')):
                    languages.append(thing)
            return sorted(languages,
                          key=lambda x: self.space[x]['inception-date'])

        @expose
        def articles():
            """Return a list of all article entries to be shown on the articles
            node.  This is a bespoke context function because we want the
            nodes to be sorted by publication date (and this is an easy way
            to do that.  There might be others.)

            """
            items = []
            for thing in self.space:
                node = self.space[thing]
                if node['type'] == 'Article':
                    items.append(thing)
            return reversed(sorted(
                items,
                key=lambda x: self.space[x]['publication-date']
            ))

        @expose
        def latest_news_item():
            return list(articles())[0]

        @expose
        def strftime(date, fmt):
            return date.strftime(fmt)

        template = self.get_template(key)
        basename = filekey(key)
        filename = os.path.join(self.output_dir, basename)
        self.render(template, filename, context)
        # sideways compatibility
        if '_' in basename:
            basename = basename.replace('_', ' ')
            filename = os.path.join(self.output_dir, basename)
            self.render(template, filename, context)