Index: django/template/__init__.py
===================================================================
--- django/template/__init__.py	(revision 7698)
+++ django/template/__init__.py	(working copy)
@@ -196,8 +196,8 @@
             ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
             self.contents[:20].replace('\n', ''))
 
-    def split_contents(self):
-        return list(smart_split(self.contents))
+    def split_contents(self, separator_chars=None):
+        return list(smart_split(self.contents, separator_chars=separator_chars))
 
 class Lexer(object):
     def __init__(self, template_string, origin):
@@ -898,11 +898,10 @@
                     dict = func(*args)
 
                     if not getattr(self, 'nodelist', False):
-                        from django.template.loader import get_template, select_template
                         if not isinstance(file_name, basestring) and is_iterable(file_name):
-                            t = select_template(file_name)
+                            t = context.select_template(file_name)
                         else:
-                            t = get_template(file_name)
+                            t = context.get_template(file_name)
                         self.nodelist = t.nodelist
                     return self.nodelist.render(context_class(dict,
                             autoescape=context.autoescape))
@@ -913,6 +912,43 @@
             return func
         return dec
 
+def parse_context_map(bits, separators=('and', ',')):
+    if len(bits) % 4 != 3:
+        raise ValueError
+    context_map = {}
+    bits = [separators[0]] + bits
+    for i in range(len(bits) / 4):
+        separator, value, as_, name = bits[4*i:4*(i+1)]
+        if not separator in separators or as_ != 'as':
+            raise ValueError
+        context_map[name] = Variable(value)
+    return context_map
+
+def parse_string_literal(bit, allow_empty=True):    
+    if len(bit) < 2 or not allow_empty and len(bit) < 3:
+        raise ValueError
+    q = bit[0]
+    if not q in ('"',"'") or bit[-1] != q:
+        raise ValueError
+    return bit[1:-1]
+
+def render_with_context_map(renderable, context_map, context):
+    """
+    renderable: a NodeList or Template
+    context_map: a dict mapping strings to Variables
+    context: a context object
+    
+    """
+    if context_map is None:
+        return renderable.render(context)
+    dict = {}
+    for name in context_map:
+        dict[name] = context_map[name].resolve(context)
+    context.update(dict)
+    output = renderable.render(context)
+    context.pop()
+    return output
+
 def get_library(module_name):
     lib = libraries.get(module_name, None)
     if not lib:
Index: django/template/defaulttags.py
===================================================================
--- django/template/defaulttags.py	(revision 7698)
+++ django/template/defaulttags.py	(working copy)
@@ -11,6 +11,7 @@
 from django.template import Node, NodeList, Template, Context, Variable
 from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
 from django.template import get_library, Library, InvalidTemplateLibrary
+from django.template import render_with_context_map, parse_context_map
 from django.conf import settings
 from django.utils.encoding import smart_str, smart_unicode
 from django.utils.itercompat import groupby
@@ -390,21 +391,15 @@
         return str(int(round(ratio)))
 
 class WithNode(Node):
-    def __init__(self, var, name, nodelist):
-        self.var = var
-        self.name = name
+    def __init__(self, context_map, nodelist):
+        self.context_map = context_map
         self.nodelist = nodelist
 
     def __repr__(self):
         return "<WithNode>"
 
     def render(self, context):
-        val = self.var.resolve(context)
-        context.push()
-        context[self.name] = val
-        output = self.nodelist.render(context)
-        context.pop()
-        return output
+        return render_with_context_map(self.nodelist, self.context_map, context)
 
 #@register.tag
 def autoescape(parser, token):
@@ -1082,7 +1077,7 @@
 #@register.tag
 def do_with(parser, token):
     """
-    Adds a value to the context (inside of this block) for caching and easy
+    Adds values to the context (inside of this block) for caching and easy
     access.
 
     For example::
@@ -1090,14 +1085,19 @@
         {% with person.some_sql_method as total %}
             {{ total }} object{{ total|pluralize }}
         {% endwith %}
+        
+        {% with person.some_sql_method as total, person.get_full_name as full_name %}
+            {{ full_name }}: {{ total }} object{{ total|pluralize }}
+        {% endwith %}
+        
     """
-    bits = list(token.split_contents())
-    if len(bits) != 4 or bits[2] != "as":
-        raise TemplateSyntaxError("%r expected format is 'value as name'" %
+    bits = list(token.split_contents(separator_chars=','))
+    try:
+        context_map = parse_context_map(bits[1:])
+    except ValueError:
+        raise TemplateSyntaxError("%r expected format is 'value as name (and value as name)*'" %
                                   bits[0])
-    var = parser.compile_filter(bits[1])
-    name = bits[3]
     nodelist = parser.parse(('endwith',))
     parser.delete_first_token()
-    return WithNode(var, name, nodelist)
+    return WithNode(context_map, nodelist)
 do_with = register.tag('with', do_with)
Index: django/template/context.py
===================================================================
--- django/template/context.py	(revision 7698)
+++ django/template/context.py	(working copy)
@@ -9,10 +9,12 @@
 
 class Context(object):
     "A stack container for variable context"
-    def __init__(self, dict_=None, autoescape=True):
+    def __init__(self, dict_=None, autoescape=True, loader=None):
         dict_ = dict_ or {}
         self.dicts = [dict_]
         self.autoescape = autoescape
+        self.template_cache = {}
+        self.loader = loader
 
     def __repr__(self):
         return repr(self.dicts)
@@ -65,6 +67,25 @@
         self.dicts = [other_dict] + self.dicts
         return other_dict
 
+    def get_template(self, template_name):
+        if not template_name in self.template_cache:
+            if self.loader is None:
+                from django.template import loader
+                tpl = loader.get_template(template_name)
+            else:
+                tpl = self.loader.get_template(template_name)
+            self.template_cache[template_name] = tpl
+        return self.template_cache[template_name]
+
+    def select_template(self, template_name_list):
+        from django.template import TemplateDoesNotExist
+        for template_name in template_name_list:
+            try:
+                return self.get_template(template_name)
+            except TemplateDoesNotExist:
+                continue
+        raise TemplateDoesNotExist, ', '.join(template_name_list)
+
 # This is a function rather than module-level procedural code because we only
 # want it to execute if somebody uses RequestContext.
 def get_standard_processors():
@@ -93,8 +114,8 @@
     Additional processors can be specified as a list of callables
     using the "processors" keyword argument.
     """
-    def __init__(self, request, dict=None, processors=None):
-        Context.__init__(self, dict)
+    def __init__(self, request, dict=None, processors=None, loader=None):
+        Context.__init__(self, dict, loader=loader)
         if processors is None:
             processors = ()
         else:
Index: django/template/loader_tags.py
===================================================================
--- django/template/loader_tags.py	(revision 7698)
+++ django/template/loader_tags.py	(working copy)
@@ -1,5 +1,6 @@
 from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
 from django.template import Library, Node, TextNode
+from django.template import render_with_context_map, parse_context_map, parse_string_literal
 from django.template.loader import get_template, get_template_from_string, find_template_source
 from django.conf import settings
 from django.utils.safestring import mark_safe
@@ -39,10 +40,9 @@
 class ExtendsNode(Node):
     must_be_first = True
 
-    def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
+    def __init__(self, nodelist, parent_name, parent_name_expr):
         self.nodelist = nodelist
         self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
-        self.template_dirs = template_dirs
 
     def __repr__(self):
         if self.parent_name_expr:
@@ -61,11 +61,9 @@
         if hasattr(parent, 'render'):
             return parent # parent is a Template object
         try:
-            source, origin = find_template_source(parent, self.template_dirs)
+            return context.get_template(parent)
         except TemplateDoesNotExist:
             raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
-        else:
-            return get_template_from_string(source, origin, parent)
 
     def render(self, context):
         compiled_parent = self.get_parent(context)
@@ -96,31 +94,16 @@
                 parent_block.nodelist = block_node.nodelist
         return compiled_parent.render(context)
 
-class ConstantIncludeNode(Node):
-    def __init__(self, template_path):
-        try:
-            t = get_template(template_path)
-            self.template = t
-        except:
-            if settings.TEMPLATE_DEBUG:
-                raise
-            self.template = None
-
-    def render(self, context):
-        if self.template:
-            return self.template.render(context)
-        else:
-            return ''
-
 class IncludeNode(Node):
-    def __init__(self, template_name):
+    def __init__(self, template_name, context_map=None):
         self.template_name = Variable(template_name)
+        self.context_map = context_map
 
     def render(self, context):
         try:
             template_name = self.template_name.resolve(context)
-            t = get_template(template_name)
-            return t.render(context)
+            tpl = context.get_template(template_name)            
+            return render_with_context_map(tpl, self.context_map, context)
         except TemplateSyntaxError, e:
             if settings.TEMPLATE_DEBUG:
                 raise
@@ -162,9 +145,9 @@
     if len(bits) != 2:
         raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
     parent_name, parent_name_expr = None, None
-    if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
-        parent_name = bits[1][1:-1]
-    else:
+    try:
+        parent_name = parse_string_literal(bits[1])
+    except ValueError:
         parent_name_expr = parser.compile_filter(bits[1])
     nodelist = parser.parse()
     if nodelist.get_nodes_by_type(ExtendsNode):
@@ -174,18 +157,26 @@
 def do_include(parser, token):
     """
     Loads a template and renders it with the current context.
+    Optionally takes a "with value as name (, value as name)*" clause.
 
     Example::
 
         {% include "foo/some_include" %}
+        {% include "foo" with value as name %}
+        {% include "foo" with value as name, bar as baz %}
     """
-    bits = token.contents.split()
-    if len(bits) != 2:
-        raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
-    path = bits[1]
-    if path[0] in ('"', "'") and path[-1] == path[0]:
-        return ConstantIncludeNode(path[1:-1])
-    return IncludeNode(bits[1])
+    bits = token.split_contents(separator_chars=',')
+    syntax_error = False
+    if len(bits) == 2:
+        return IncludeNode(bits[1])
+    if len(bits) > 2 and bits[2] == 'with':
+        try:
+            context_map = parse_context_map(bits[3:])            
+            return IncludeNode(bits[1], context_map)
+        except ValueError:
+            raise ValueError, bits[3:]
+    raise TemplateSyntaxError, "%r tag takes the name of the template to be included and an optional 'with value as name' clause." % bits[0]    
+    
 
 register.tag('block', do_block)
 register.tag('extends', do_extends)
Index: django/utils/text.py
===================================================================
--- django/utils/text.py	(revision 7698)
+++ django/utils/text.py	(working copy)
@@ -1,7 +1,7 @@
 import re
 from django.conf import settings
 from django.utils.encoding import force_unicode
-from django.utils.functional import allow_lazy
+from django.utils.functional import allow_lazy, memoize
 from django.utils.translation import ugettext_lazy
 
 # Capitalizes the first letter of a string.
@@ -196,23 +196,36 @@
     return str(ustring_re.sub(fix, s))
 javascript_quote = allow_lazy(javascript_quote, unicode)
 
-smart_split_re = re.compile('("(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|[^\\s]+)')
-def smart_split(text):
+quoted_string_pattern = '"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\''
+def get_smart_split_regexp(chars):
+    if chars is None:
+        return re.compile('(%s|[^\\s]+)' % quoted_string_pattern)
+    return re.compile('(%s|[^\\s%s]+|[%s])' % (quoted_string_pattern, chars, chars))
+get_smart_split_regexp = memoize(get_smart_split_regexp, {}, 1)
+
+def smart_split(text, separator_chars=None):
     r"""
     Generator that splits a string by spaces, leaving quoted phrases together.
     Supports both single and double quotes, and supports escaping quotes with
     backslashes. In the output, strings will keep their initial and trailing
     quote marks.
-
+    A `separator_chars` string can be given to additionally split by each 
+    contained character. Matched characters will be included in the output.
+    
     >>> list(smart_split(r'This is "a person\'s" test.'))
     [u'This', u'is', u'"a person\\\'s"', u'test.']
    	>>> list(smart_split(r"Another 'person\'s' test.")) 
    	[u'Another', u"'person's'", u'test.']
    	>>> list(smart_split(r'A "\"funky\" style" test.')) 
    	[u'A', u'""funky" style"', u'test.']
+    >>> list(smart_split('a, b,c ,d, e ,f', separator_chars=','))
+    [u'a', u',', u'b', u',', u'c', u',', u'd', u',', u'e', u',', u'f']
+    >>> list(smart_split('a=1, b=2 , c = 3', separator_chars=',='))
+    [u'a', u'=', u'1', u',', u'b', u'=', u'2', u',', u'c', u'=', u'3']   	
     """
     text = force_unicode(text)
-    for bit in smart_split_re.finditer(text):
+    regexp = get_smart_split_regexp(separator_chars)
+    for bit in regexp.finditer(text):
         bit = bit.group(0)
         if bit[0] == '"' and bit[-1] == '"':
             yield '"' + bit[1:-1].replace('\\"', '"').replace('\\\\', '\\') + '"'
Index: tests/regressiontests/templates/tests.py
===================================================================
--- tests/regressiontests/templates/tests.py	(revision 7698)
+++ tests/regressiontests/templates/tests.py	(working copy)
@@ -611,6 +611,26 @@
             'include02': ('{% include "basic-syntax02" %}', {'headline': 'Included'}, "Included"),
             'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
             'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
+            'include-with01': ('{% include "basic-syntax02" with foo as headline %}', {'foo': 'Included'}, "Included"),
+            'include-with02': ('{% include "basic-syntax03" with foo as first, bar as second %}', {"foo" : 1, "bar" : 2}, "1 --- 2"),
+            'recursive-include': ('{% for item in items %}{{ item.label }}{% if not item.children|length_is:0 %}{% with item.children as items %}({% include "recursive-include" %}){% endwith %}{% endif %}{% endfor %}', {
+                'items': [
+                    {'label': 1, 'children': [
+                        {'label': 2, 'children': [
+                            {'label': 3, 'children': []},
+                            {'label': 4, 'children': []},
+                        ]},
+                        {'label': 5, 'children': [
+                             {'label': 6, 'children': [
+                                 {'label': 7, 'children': [
+                                     {'label': 8, 'children': []},	
+                                 ]},
+                                {'label': 9, 'children': []},	
+                             ]},
+                        ]},
+                    ]},
+                ],
+            }, '1(2(34)5(6(7(8)9)))'),
 
             ### NAMED ENDBLOCKS #######################################################
 
@@ -854,10 +874,20 @@
             ### WITH TAG ########################################################
             'with01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, '50'),
             'with02': ('{{ key }}{% with dict.key as key %}{{ key }}-{{ dict.key }}-{{ key }}{% endwith %}{{ key }}', {'dict': {'key':50}}, ('50-50-50', 'INVALID50-50-50INVALID')),
+            'with03': ('{% with a as b and b as a %}{{ a }}{{ b }}{% endwith %}', {'a': 'A', 'b': 'B'}, 'BA'),
+            'with04': ('{% with a as b, b as a %}{{ a }}{{ b }}{% endwith %}', {'a': 'A', 'b': 'B'}, 'BA'),
+            'with05': ('{% with a as b, b as a, "," as s %}{{ a }}{{ s }}{{ b }}{% endwith %}', {'a': 'A', 'b': 'B'}, 'B,A'),
+            'with06': ('{% with a as b and \',\' as s, b as a %}{{ a }}{{ s }}{{ b }}{% endwith %}', {'a': 'A', 'b': 'B'}, 'B,A'),
 
             'with-error01': ('{% with dict.key xx key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError),
             'with-error02': ('{% with dict.key as %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError),
-
+            'with-error03': ('{% with a as x, as y %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),
+            'with-error04': ('{% with a as x, b as %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),
+            'with-error05': ('{% with as x, b as y %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),                        
+            'with-error06': ('{% with a as x | b as y %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),
+            'with-error07': ('{% with a as x xxx b as y %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),
+            'with-error08': ('{% with a xx x, b xx y %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),
+                        
             ### NOW TAG ########################################################
             # Simple case
             'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
Index: tests/regressiontests/text/tests.py
===================================================================
--- tests/regressiontests/text/tests.py	(revision 7698)
+++ tests/regressiontests/text/tests.py	(working copy)
@@ -15,6 +15,10 @@
 [u'"a', u"'one"]
 >>> print list(smart_split(r'''all friends' tests'''))[1]
 friends'
+>>> list(smart_split('a, b,c ,d, e ,f', separator_chars=','))
+[u'a', u',', u'b', u',', u'c', u',', u'd', u',', u'e', u',', u'f']
+>>> list(smart_split('a=1, b=2 , c = 3', separator_chars=',='))
+[u'a', u'=', u'1', u',', u'b', u'=', u'2', u',', u'c', u'=', u'3']
 
 ### urlquote #############################################################
 >>> from django.utils.http import urlquote, urlquote_plus
