Skip to content

Commit

Permalink
Adds 'active' template helper and improve testability
Browse files Browse the repository at this point in the history
  • Loading branch information
jpsca committed Dec 31, 2012
1 parent 2fdf91b commit 260f234
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 48 deletions.
6 changes: 3 additions & 3 deletions AUTHORS.md
Expand Up @@ -5,10 +5,10 @@ Clay is written and maintained by the Lúcuma Team and various contributors.


Project Leader / Developer: Project Leader / Developer:


- Juan-Pablo Scaletti <juanpablo@lucumalabs.com> - Juan-Pablo Scaletti (jpscaletti / lucuma)




Contributors: Contributors:


- Muhammad Alkarouri (https://github.com/malkarouri) - Muhammad Alkarouri (malkarouri)
- Alexander Zayats (https://github.com/z4y4ts) - Alexander Zayats (z4y4ts)
13 changes: 5 additions & 8 deletions CHANGES.md
Expand Up @@ -3,11 +3,8 @@


## Version 2.0 ## Version 2.0


Complete TDD rewrite in Flask + few awesome Lucuma libraries. TDD rewrite. Most important new features:
Most important new features: - _index.html now is viewable in run mode.

- No need to filter non-html files. Only those ending in ".html" or ".tmpl" are processed as templates.
- _index.html now is viewable in run mode. - Adaptative run port (if it is taken, Clay try to use the next one).
- Adaptative run port (if it is taken, Clay try to use the next one). - The "not found" page now shows the real missing templates (it could be "imported" inside the current page).
- No need to filter non-html files. Only those ending in ".html" or ".tmpl" are processed as templates.


7 changes: 1 addition & 6 deletions README.md
Expand Up @@ -48,11 +48,6 @@ To generate a static version of your site, stop the server (with `Control + C`)
and all the templates will be processed and the result stored inside the `build` folder. and all the templates will be processed and the result stored inside the `build` folder.




## Not just for HTML

Clay can automatically compiles Less, Sass, CleverCSS & CoffeeScript files directly from your `static` dir (Less and CoffeeScript has to be installed in node first).


## How to install ## How to install


Just run Just run
Expand Down Expand Up @@ -92,6 +87,6 @@ Jinja2 templates are much more than just template inheritance. For more advaced




--------------------------------------- ---------------------------------------
© by [Lúcuma] (http://lucumalabs.com).<br /> © [Lúcuma] (http://lucumalabs.com).<br />
See `AUTHORS.md` for more details.<br /> See `AUTHORS.md` for more details.<br />
License: [MIT License] (http://www.opensource.org/licenses/mit-license.php). License: [MIT License] (http://www.opensource.org/licenses/mit-license.php).
9 changes: 5 additions & 4 deletions clay/main.py
Expand Up @@ -17,7 +17,7 @@


from .helpers import (read_content, make_dirs, create_file, from .helpers import (read_content, make_dirs, create_file,
copy_if_updated, get_updated_datetime) copy_if_updated, get_updated_datetime)
from .tglobals import link_to, to_unicode from .tglobals import link_to, active, to_unicode




SOURCE_DIRNAME = 'source' SOURCE_DIRNAME = 'source'
Expand All @@ -30,9 +30,9 @@
DEFAULT_HOST = '0.0.0.0' DEFAULT_HOST = '0.0.0.0'
DEFAULT_PORT = 8080 DEFAULT_PORT = 8080


WELCOME = " # Clay (by Lucuma labs)" WELCOME = u" # Clay (by Lucuma labs)"
ADDRINUSE = " ---- Address already in use. Trying another port..." ADDRINUSE = u" ---- Address already in use. Trying another port..."
SOURCE_NOT_FOUND = """We couldn't found a "%s" dir. SOURCE_NOT_FOUND = u"""We couldn't found a "%s" dir.
Are you sure you're in the correct folder? """ % SOURCE_DIRNAME Are you sure you're in the correct folder? """ % SOURCE_DIRNAME


rx_abs_url = re.compile(r'\s(src|href)=[\'"](\/(?:[a-z0-9][^\'"]*)?)[\'"]', rx_abs_url = re.compile(r'\s(src|href)=[\'"](\/(?:[a-z0-9][^\'"]*)?)[\'"]',
Expand Down Expand Up @@ -79,6 +79,7 @@ def inject_globals():


'now': datetime.utcnow(), 'now': datetime.utcnow(),
'enumerate': enumerate, 'enumerate': enumerate,
'active': active,
'link_to': link_to, 'link_to': link_to,
} }


Expand Down
52 changes: 28 additions & 24 deletions clay/tglobals.py
Expand Up @@ -5,10 +5,7 @@
from xml.sax.saxutils import quoteattr from xml.sax.saxutils import quoteattr


from jinja2 import Markup from jinja2 import Markup
from flask import request, url_for from flask import request


rx_url = re.compile(ur'^([a-z]{3,7}:(//)?)?([^/:]+%s|([0-9]{1,3}\.){3}[0-9]{1,3})(:[0-9]+)?(\/.*)?$')




def to_unicode(s, encoding='utf8', errors='strict'): def to_unicode(s, encoding='utf8', errors='strict'):
Expand All @@ -17,6 +14,23 @@ def to_unicode(s, encoding='utf8', errors='strict'):
return s.decode(encoding, errors) return s.decode(encoding, errors)




def norm_url(url):
return '/' + url.strip('/')


def active(url='/', partial=False):
path = request.path.rstrip('/')
resp = u''
patterns = url if isinstance(url, (list, tuple)) else [url]

for url in patterns:
url = norm_url(url)
if path == url or (partial and path.startswith(url)):
return 'active'

return resp


def format_html_attrs(**kwargs): def format_html_attrs(**kwargs):
"""Generate HTML attributes from the provided keyword arguments. """Generate HTML attributes from the provided keyword arguments.
Expand Down Expand Up @@ -55,7 +69,7 @@ def format_html_attrs(**kwargs):
return u' '.join(attrs) return u' '.join(attrs)




def link_to(text='', endpoint='', wrapper=None, partial=False, **kwargs): def link_to(text='', url='/', wrapper=None, partial=False, **kwargs):
"""Build an HTML anchor element for the provided URL. """Build an HTML anchor element for the provided URL.
If the url match the beginning of that in the current request, an `active` If the url match the beginning of that in the current request, an `active`
class is added. This is intended to be use to build navigation links. class is added. This is intended to be use to build navigation links.
Expand All @@ -81,10 +95,9 @@ class is added. This is intended to be use to build navigation links.
:param text: :param text:
The text (or HTML) of the link. The text (or HTML) of the link.
:param endpoint: :param url:
URL or endpoint name. This can also be a *list* of URLs and/or This can also be a *list* of URLs. The first one will be used for the
endpoint names. The first one will be used for the link, the rest only link, the rest only to match the current page
to match the current page
:param wrapper: :param wrapper:
Optional tag name of a wrapper element for the link. Optional tag name of a wrapper element for the link.
Expand All @@ -95,27 +108,18 @@ class is added. This is intended to be use to build navigation links.
u'<li title="Hi"><a href="/hello/">Hello</a></li>' u'<li title="Hi"><a href="/hello/">Hello</a></li>'
:param partial: :param partial:
If True, the endpoint will be matched against the beginning of the If True, the url will be matched against the beginning of the
current URL. For instance, if the current URL is `/foo/bar/123/`, current URL. For instance, if the current URL is `/foo/bar/123/`,
an endpoint like `/foo/bar/` will be considered a match. an url like `/foo/bar/` will be considered a match.
""" """
path = request.path.rstrip('/')

patterns = endpoint if isinstance(endpoint, (list, tuple)) else [endpoint]
patterns = [p
if p.startswith('/') or re.match(rx_url, p) else url_for(p)
for p in patterns]

classes = kwargs.pop('classes', '').strip() classes = kwargs.pop('classes', '').strip()
for url in patterns: active_class = active(url, partial)
url = url.rstrip('/') classes += u' ' + active_class if active_class else u''
if path == url or (partial and path.startswith(url)): url = url[0] if isinstance(url, (list, tuple)) else url
classes += ' active'
break


data = { data = {
'url': patterns[0], 'url': url,
'text': text, 'text': text,
'attrs': format_html_attrs(classes=classes, **kwargs), 'attrs': format_html_attrs(classes=classes, **kwargs),
} }
Expand Down
4 changes: 2 additions & 2 deletions tests/helpers.py
Expand Up @@ -22,12 +22,12 @@
BUILD_DIR = join(TESTS, 'build') BUILD_DIR = join(TESTS, 'build')




@pytest.fixture(scope="module") @pytest.fixture()
def c(): def c():
return Clay(TESTS) return Clay(TESTS)




@pytest.fixture(scope="module") @pytest.fixture()
def t(c): def t(c):
return c.get_test_client() return c.get_test_client()


Expand Down
56 changes: 55 additions & 1 deletion tests/test_tglobals.py
@@ -1,11 +1,20 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import from __future__ import absolute_import


from clay.tglobals import link_to from clay.tglobals import link_to, active


from .helpers import * from .helpers import *




def setup_module():
remove_test_dirs()
make_dirs(SOURCE_DIR)


def teardown_module():
remove_test_dirs()


def _test_link_to(path): def _test_link_to(path):
html = link_to(u'Hello', '/hello/', title='click me') html = link_to(u'Hello', '/hello/', title='click me')
expected = u'<a href="/hello/" title="click me">Hello</a>' expected = u'<a href="/hello/" title="click me">Hello</a>'
Expand Down Expand Up @@ -45,3 +54,48 @@ def test_link_to(c):
with c.app.test_request_context(path, method='GET'): with c.app.test_request_context(path, method='GET'):
_test_link_to(path) _test_link_to(path)



def test_link_to_in_templates(t):
setup_module()
make_dirs(SOURCE_DIR)

path = 'aaaa.html'
content = u"{{ link_to('', '%s') }}" % path
create_file(get_source_path(path), content)

expected = u'<a href="%s" class="active"></a>' % path
resp = t.get('/' + path)
assert resp.data == expected


def _test_active_case(path, as_expected, partial=False):
html = active(path, partial=partial)
assert (html == 'active') is as_expected


def _test_active(path):
_test_active_case('/hello/', False)
_test_active_case(path, True)
_test_active_case('/foo/', True, partial=True)
_test_active_case(['/hello/', path], True)
_test_active_case(['/hello/', '/world/'], False)


def test_active(c):
path = '/foo/bar/'
with c.app.test_request_context(path, method='GET'):
_test_active(path)


def test_active_in_templates(t):
setup_module()
make_dirs(SOURCE_DIR)

path = 'bbbb.html'
content = u'''class="{{ active('%s') }}"''' % path
create_file(get_source_path(path), content)

expected = u'class="active"'
resp = t.get('/' + path)
assert resp.data == expected

0 comments on commit 260f234

Please sign in to comment.