Repository: Keats/tera
Pull request #61 (2016-10-03 20:46:52)

Support indexing


Patch:
diff --git a/README.md b/README.md
index 1dd921e..e73cbb8 100644
--- a/README.md
+++ b/README.md
@@ -84,7 +84,10 @@ Notice that the name of the template is based on the root of the template direct
 
 ## Template writer documentation
 ### Variables
-You can access variables of the context by using the `{{ my_variable_name }}` construct. You can access attributes by using the dot (`.`) like `{{ product.name }}`.
+You can access variables of the context by using the `{{ my_variable_name }}` construct. 
+You can access attributes by using the dot (`.`) like `{{ product.name }}`.
+You can access specific members of an array or tuple by using the `.i` notation where `i` is a zero-based index.
+
 You can also do some maths: `{{ product.price + 10 }}`. If `product.price` is not a number type, the `render` method will return an error.
 
 ### If
diff --git a/src/render.rs b/src/render.rs
index 6d3e57f..ea78993 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -65,12 +65,19 @@ impl<'a> Renderer<'a> {
         }
     }
 
+    // Converts a dotted path to a json pointer one
+    // TODO: should belong on the context itself eventually once
+    // we move from json
+    fn get_json_pointer(&self, key: &str) -> String {
+        ["/", &key.replace(".", "/")].join("")
+    }
+
     // Lookup a variable name from the context and takes into
     // account for loops variables
     fn lookup_variable(&self, key: &str) -> TeraResult<Value> {
         // Look in the plain context if we aren't in a for loop
         if self.for_loops.is_empty() {
-            return self.context.lookup(key).cloned()
+            return self.context.pointer(&self.get_json_pointer(key)).cloned()
                 .ok_or_else(|| FieldNotFound(key.to_string()));
         }
 
@@ -84,8 +91,7 @@ impl<'a> Renderer<'a> {
                 // might be a struct or some nested structure
                 if key.contains('.') {
                     let new_key = key.split_terminator('.').skip(1).collect::<Vec<&str>>().join(".");
-                    return value.lookup(&new_key).cloned()
-                        .ok_or_else(|| FieldNotFound(key.to_string()));
+                    return value.pointer(&self.get_json_pointer(&new_key)).cloned().ok_or_else(|| FieldNotFound(key.to_string()));
                 } else {
                     return Ok(value.clone());
                 }
@@ -102,7 +108,8 @@ impl<'a> Renderer<'a> {
 
         // dummy statement to satisfy the compiler
         // TODO: make it so that's not needed
-        self.context.lookup(key).cloned().ok_or_else(|| FieldNotFound(key.to_string()))
+        self.context.pointer(&self.get_json_pointer(key)).cloned()
+            .ok_or_else(|| FieldNotFound(key.to_string()))
     }
 
     // Gets an identifier and return its json value
@@ -553,4 +560,17 @@ mod tests {
 
         assert_eq!(result.unwrap(), "HELLO".to_owned());
     }
+
+    #[test]
+    fn test_render_index_array() {
+        let mut context = Context::new();
+        context.add("my_arr", &vec![1, 2, 3]);
+        context.add("my_arr2", &vec![(1,2,3), (1,2,3), (1,2,3)]);
+        let result = render_template(
+            "{{ my_arr.1 }}{{ my_arr2.1.1 }}",
+            context
+        );
+
+        assert_eq!(result.unwrap(), "22".to_owned());
+    }
 }


Test patch:
diff --git a/tests/expected/indexing.html b/tests/expected/indexing.html
new file mode 100644
index 0000000..e09405f
--- /dev/null
+++ b/tests/expected/indexing.html
@@ -0,0 +1,3 @@
+My review
+1
+22
diff --git a/tests/render_ok.rs b/tests/render_ok.rs
index 6ec3426..f2bb144 100644
--- a/tests/render_ok.rs
+++ b/tests/render_ok.rs
@@ -28,6 +28,8 @@ fn assert_template_ok(path: &str, base_path: Option<&str>) {
     context.add("number_reviews", &2);
     context.add("show_more", &true);
     context.add("reviews", &vec![Review::new(), Review::new()]);
+    context.add("a_tuple", &(1, 2, 3));
+    context.add("an_array_of_tuple", &vec![(1, 2, 3), (1, 2, 3)]);
     let empty: Vec<Review> = Vec::new();
     context.add("empty", &empty);
 
@@ -102,3 +104,8 @@ fn test_ok_filters_template() {
 fn test_ok_variable_tests() {
     assert_template_ok("tests/templates/variable_tests.html", None);
 }
+
+#[test]
+fn test_ok_indexing() {
+    assert_template_ok("tests/templates/indexing.html", None);
+}
diff --git a/tests/templates/indexing.html b/tests/templates/indexing.html
new file mode 100644
index 0000000..51871d5
--- /dev/null
+++ b/tests/templates/indexing.html
@@ -0,0 +1,3 @@
+{{ reviews.1.title }}
+{{ a_tuple.0 }}
+{% for u in an_array_of_tuple %}{{ u.1 }}{% endfor %}