{"thread":{"id":"24968","subject":"[PATCH] Mockup an svndiff version 0 parser","startedAt":"2010-09-04T07:43:33Z","lastAt":"2010-09-04T12:20:56Z","messageCount":4,"participants":["Ramkumar Ramachandra","David Barr"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"149823","messageId":"1283586214-31204-1-git-send-email-artagnon@gmail.com","threadId":"24968","inReplyTo":null,"subject":"[PATCH] Mockup an svndiff version 0 parser","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2010-09-04T07:43:33Z","receivedAt":"2010-09-04T07:43:33Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Hi,\n\nI finally rolled up my sleeves and wrote that svndiff0 parser\nyesterday. I've tested it with a few source-target combinations, and\nit seems to work fine. It's based on `master`, now that `jn/svn-fe`\nhas graduated. It's not intended for inclusion yet- it's more of a\n\"here's what I've been upto, and I'd like some feedback\".\nTODO:\n1. malloc, realloc and free calls: I should factor these out to use\nthe obj_pool library in vcs-svn/\n2. line_buffer. Only operations on stdin are currently\nbuffered. Should we extend the line_buffer library to be more generic?\n3. Performance: The applier I've written is VERY naively. I can't help\nbut think that there must be a better way to do it. I still don't have\na solution though- the main problem is copyfrom_target.\n4. Using the context dumpfilev3. The svndiff0 itself has no end\nmarkers and it's difficult to say when it's going to end. Instead of\nunconditionally waiting for more data (which is a problem when the\nconnection breaks), limit the number of bytes to parse using the\nContent-Length header in the dumpfile v3.\n\nThanks.\n\np.s- I'll be MIA for a few weeks; exams are coming up in a week.\n\nRamkumar Ramachandra (1):\n  vcs-svn: Add an svndiff0 parser\n\n vcs-svn/Makefile      |    2 +\n vcs-svn/line_buffer.c |    7 +-\n vcs-svn/line_buffer.h |    2 +-\n vcs-svn/svndiff.c     |  240 +++++++++++++++++++++++++++++++++++++++++++++++++\n vcs-svn/svndiff.h     |   41 +++++++++\n 5 files changed, 288 insertions(+), 4 deletions(-)\n create mode 100644 vcs-svn/Makefile\n create mode 100644 vcs-svn/svndiff.c\n create mode 100644 vcs-svn/svndiff.h\n\n-- \n1.7.2.2.409.gdbb11.dirty\n"},{"id":"149824","messageId":"1283586214-31204-2-git-send-email-artagnon@gmail.com","threadId":"24968","inReplyTo":"1283586214-31204-1-git-send-email-artagnon@gmail.com","subject":"[WIP PATCH] vcs-svn: Add an svndiff0 parser","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2010-09-04T07:43:34Z","receivedAt":"2010-09-04T07:43:34Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Add a parser to apply one window from an svndiff version 0 stream to a\ngiven source. svndiff version 0 is used by dumpfile version 3; this is\none step towards getting svn-fe to parse the output produced by\nsvnrdump. The svndiff format is described in $SVN_TRUNK/notes/svndiff.\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n vcs-svn/Makefile      |    2 +\n vcs-svn/line_buffer.c |    7 +-\n vcs-svn/line_buffer.h |    2 +-\n vcs-svn/svndiff.c     |  240 +++++++++++++++++++++++++++++++++++++++++++++++++\n vcs-svn/svndiff.h     |   41 +++++++++\n 5 files changed, 288 insertions(+), 4 deletions(-)\n create mode 100644 vcs-svn/Makefile\n create mode 100644 vcs-svn/svndiff.c\n create mode 100644 vcs-svn/svndiff.h\n\ndiff --git a/vcs-svn/Makefile b/vcs-svn/Makefile\nnew file mode 100644\nindex 0000000..62cf093\n--- /dev/null\n+++ b/vcs-svn/Makefile\n@@ -0,0 +1,2 @@\n+default: svndiff.c line_buffer.c\n+\t$(CC) -I../ -g3 -O0 -o svndiff svndiff.c line_buffer.c\ndiff --git a/vcs-svn/line_buffer.c b/vcs-svn/line_buffer.c\nindex 1543567..17618c0 100644\n--- a/vcs-svn/line_buffer.c\n+++ b/vcs-svn/line_buffer.c\n@@ -66,15 +66,16 @@ char *buffer_read_string(uint32_t len)\n \treturn ferror(infile) ? NULL : s;\n }\n \n-void buffer_copy_bytes(uint32_t len)\n+void buffer_copy_bytes(uint32_t len, FILE *dst)\n {\n \tuint32_t in;\n+\tdst = dst ? dst : stdout;\n \twhile (len > 0 && !feof(infile) && !ferror(infile)) {\n \t\tin = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN;\n \t\tin = fread(byte_buffer, 1, in, infile);\n \t\tlen -= in;\n-\t\tfwrite(byte_buffer, 1, in, stdout);\n-\t\tif (ferror(stdout)) {\n+\t\tfwrite(byte_buffer, 1, in, dst);\n+\t\tif (ferror(dst)) {\n \t\t\tbuffer_skip_bytes(len);\n \t\t\treturn;\n \t\t}\ndiff --git a/vcs-svn/line_buffer.h b/vcs-svn/line_buffer.h\nindex 9c78ae1..fe67a22 100644\n--- a/vcs-svn/line_buffer.h\n+++ b/vcs-svn/line_buffer.h\n@@ -5,7 +5,7 @@ int buffer_init(const char *filename);\n int buffer_deinit(void);\n char *buffer_read_line(void);\n char *buffer_read_string(uint32_t len);\n-void buffer_copy_bytes(uint32_t len);\n+void buffer_copy_bytes(uint32_t len, FILE *dst);\n void buffer_skip_bytes(uint32_t len);\n void buffer_reset(void);\n \ndiff --git a/vcs-svn/svndiff.c b/vcs-svn/svndiff.c\nnew file mode 100644\nindex 0000000..2d66dce\n--- /dev/null\n+++ b/vcs-svn/svndiff.c\n@@ -0,0 +1,241 @@\n+#include \"git-compat-util.h\"\n+#include \"line_buffer.h\"\n+#include \"svndiff.h\"\n+\n+#define DEBUG 1\n+\n+#define SVN_DELTA_WINDOW_SIZE 102400\n+#define MAX_ENCODED_INT_LEN 10\n+#define MAX_INSTRUCTION_LEN (2*MAX_ENCODED_INT_LEN+1)\n+#define MAX_INSTRUCTION_SECTION_LEN (SVN_DELTA_WINDOW_SIZE*MAX_INSTRUCTION_LEN)\n+static char buf[SVN_DELTA_WINDOW_SIZE];\n+\n+/* Remove when linking to gitcore */\n+void die(const char *err, ...)\n+{\n+\tva_list params;\n+\tva_start(params, err);\n+\tvfprintf(stderr, err, params);\n+\tprintf(\"\\n\");\n+\tva_end(params);\n+\texit(128);\n+}\n+\n+/* Return the number of bytes read */\n+size_t read_one_size(size_t *size)\n+{\n+\tunsigned char c;\n+\tsize_t result, bsize;\n+\tresult = 0;\n+\tbsize = 0;\n+\n+\twhile (1)\n+\t{\n+\t\tfread(&c, 1, 1, stdin);\n+\t\tresult = (result << 7) | (c & 127);\n+\t\tbsize ++;\n+\t\tif (!(c & 128))\n+\t\t\t/* No continuation bit */\n+\t\t\tbreak;\n+\t}\n+\t*size = result;\n+\treturn bsize;\n+}\n+\n+/* Return the number of bytes read */\n+size_t read_one_instruction(struct svndiff_instruction *op)\n+{\n+\tunsigned char c;\n+\tsize_t action, bsize;\n+\tbsize = 0;\n+\n+\t/* Read the 1-byte instruction-selector */\n+\tfread(&c, 1, 1, stdin);\n+\tbsize ++;\n+\n+\t/* Decode the instruction selector from the two higher order\n+\t   bits; the remaining 6 bits may contain the length */\n+\taction = (c >> 6) & 3;\n+\tif (action >= 3)\n+\t\tdie(\"Invalid instruction %d\", action);\n+\n+\top->action_code = (enum svndiff_action)(action);\n+\n+\t/* Attempt to extract the length length from the remaining\n+\t   bits */\n+\top->length = c & 63;\n+\tif (op->length == 0)\n+\t{\n+\t\tbsize += read_one_size(&(op->length));\n+\t\tif (op->length == 0)\n+\t\t\tdie(\"Zero length instruction\");\n+\t}\n+\t/* Offset is present if action is copyfrom_source or\n+\t   copyfrom_target */\n+\tif (action != copyfrom_new)\n+\t\tbsize += read_one_size(&(op->offset));\n+\treturn bsize;\n+}\n+\n+size_t read_instructions(struct svndiff_window *window, size_t *ninst)\n+{\n+\tsize_t tpos = 0, npos, bsize;\n+\tstruct svndiff_instruction *op;\n+\tnpos = 0;\n+\tbsize = 0;\n+\t*ninst = 0;\n+\t\n+\twhile (bsize < window->ins_len)\n+\t{\n+\t\t++(*ninst);\n+\t\twindow->ops = realloc(window->ops, (*ninst) * sizeof(*op));\n+\t\top = window->ops + (*ninst) - 1;\n+\t\tbsize += read_one_instruction(op);\n+\n+\t\tif (DEBUG)\n+\t\t\tfprintf(stderr, \"Instruction: %d %d %d (%d)\\n\",\n+\t\t\t\top->action_code, op->length, op->offset, bsize);\n+\n+\t\tif (op == NULL)\n+\t\t\tdie(\"Invalid diff stream: \"\n+\t\t\t    \"instruction %d cannot be decoded\", *ninst);\n+\t\telse if (op->length == 0)\n+\t\t\tdie(\"Invalid diff stream: \"\n+\t\t\t    \"instruction %d has length zero\", *ninst);\n+\t\telse if (op->length > window->tview_len - tpos)\n+\t\t\tdie(\"Invalid diff stream: \"\n+\t\t\t    \"instruction %d overflows the target view\", *ninst);\n+\n+\t\tswitch (op->action_code)\n+\t\t{\n+\t\tcase copyfrom_source:\n+\t\t\tif (op->length > window->sview_len - op->offset ||\n+\t\t\t    op->offset > window->sview_len)\n+\t\t\t\tdie(\"Invalid diff stream: \"\n+\t\t\t\t    \"[src] instruction %d overflows \"\n+\t\t\t\t    \" the source view\", *ninst);\n+\t\t\tbreak;\n+\t\tcase copyfrom_target:\n+\t\t\tif (op->offset >= tpos)\n+\t\t\t\tdie(\"Invalid diff stream: \"\n+\t\t\t\t    \"[tgt] instruction %d starts \"\n+\t\t\t\t    \"beyond the target view position\", *ninst);\n+\t\t\tbreak;\n+\t\tcase copyfrom_new:\n+\t\t\tif (op->length > window->newdata_len - npos)\n+\t\t\t\tdie(\"Invalid diff stream: \"\n+\t\t\t\t    \"[new] instruction %d overflows \"\n+\t\t\t\t    \"the new data section\", *ninst);\n+\t\t\tnpos += op->length;\n+\t\t\tbreak;\n+\t\t}\n+\t\ttpos += op->length;\n+\t}\n+\n+\tif (tpos != window->tview_len)\n+\t\tdie(\"Delta does not fill the target window\");\n+\tif (npos != window->newdata_len)\n+\t\tdie(\"Delta does not contain enough new data\");\n+\treturn bsize;\n+}\n+\n+size_t read_window_header(struct svndiff_window *window)\n+{\n+\tsize_t bsize = 0;\n+\n+\t/* Read five sizes; various offsets and lengths */\n+\tbsize += read_one_size(&(window->sview_offset));\n+\tbsize += read_one_size(&(window->sview_len));\n+\tbsize += read_one_size(&(window->tview_len));\n+\tbsize += read_one_size(&(window->ins_len));\n+\tbsize += read_one_size(&(window->newdata_len));\n+\n+\tif (window->tview_len > SVN_DELTA_WINDOW_SIZE ||\n+\t    window->sview_len > SVN_DELTA_WINDOW_SIZE ||\n+\t    window->newdata_len > SVN_DELTA_WINDOW_SIZE + MAX_ENCODED_INT_LEN ||\n+\t    window->ins_len > MAX_INSTRUCTION_SECTION_LEN)\n+\t\tdie(\"Svndiff contains a window that's too large\");\n+\n+\t/* Check for integer overflow */\n+\tif (window->ins_len + window->newdata_len < window->ins_len\n+\t    || window->sview_len + window->tview_len < window->sview_len\n+\t    || window->sview_offset + window->sview_len < window->sview_offset)\n+\t\tdie(\"Svndiff contains corrupt window header\");\n+\n+\tif (DEBUG)\n+\t\tfprintf(stderr, \"Window header: %d %d %d %d %d\\n\",\n+\t\t\twindow->sview_offset, window->sview_len,\n+\t\t\twindow->tview_len, window->ins_len, window->newdata_len);\n+\treturn bsize;\n+}\n+\n+void drive_window(struct svndiff_window *window, FILE *src_fd)\n+{\n+\tstruct svndiff_instruction *op;\n+\tsize_t ninst;\n+\tFILE *target_fd;\n+\tlong target_fd_end;\n+\n+\t/* Populate the first five fields of the the window object\n+\t   with data from the stream */\t\n+\tread_window_header(window);\n+\n+\t/* Read instructions of length ins_len into window->ops\n+\t   performing memory allocations as necessary */\n+\tread_instructions(window, &ninst);\n+\n+\t/* The Applier */\n+\t/* We're now looking at new_data; read ahead only in the\n+\t   copyfrom_new case */\t\n+\ttarget_fd = tmpfile();\n+\tfor (op = window->ops; ninst-- > 0; op++) {\n+\t\tswitch (op->action_code) {\n+\t\tcase copyfrom_source:\n+\t\t\tfseek(src_fd, op->offset, SEEK_SET);\n+\t\t\tfread(buf, op->length, 1, src_fd);\n+\t\t\tfwrite(buf, op->length, 1, target_fd);\n+\t\t\tbreak;\n+\t\tcase copyfrom_target:\n+\t\t\tfseek(target_fd, op->offset, SEEK_SET);\n+\t\t\tfread(buf, op->length, 1, target_fd);\n+\t\t\tfseek(target_fd, 0, SEEK_END);\n+\t\t\tfwrite(buf, op->length, 1, target_fd);\n+\t\t\tbreak;\n+\t\tcase copyfrom_new:\n+\t\t\tfseek(target_fd, 0, SEEK_END);\n+\t\t\tbuffer_copy_bytes(op->length, target_fd);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tfree(window->ops);\n+\ttarget_fd_end = ftell(target_fd);\n+\tfseek(target_fd, 0, SEEK_SET);\n+\tfread(buf, target_fd_end, 1, target_fd);\n+\tfwrite(buf, target_fd_end, 1, stdout);\n+\tfclose (target_fd);\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tint version;\n+\tstruct svndiff_window *window;\n+\tFILE *src_fd;\n+\tbuffer_init(NULL);\n+\n+\t/* Read off the 4-byte header: \"SVN\\0\" */\n+\tfread(&buf, 4, 1, stdin);\n+\tversion = atoi(buf + 3);\n+\tif (version != 0)\n+\t\tdie(\"Version %d unsupported\", version);\n+\n+\t/* Setup the source to apply windows to */\n+\tsrc_fd = fopen(argv[1], \"r\");\n+\n+\t/* Read and drive the first window */\n+\twindow = malloc(sizeof(*window));\n+\tdrive_window(window, src_fd);\n+\tfree(window);\n+\n+\tbuffer_deinit();\n+\treturn 0;\n+}\ndiff --git a/vcs-svn/svndiff.h b/vcs-svn/svndiff.h\nnew file mode 100644\nindex 0000000..6d0607f\n--- /dev/null\n+++ b/vcs-svn/svndiff.h\n@@ -0,0 +1,41 @@\n+enum svndiff_action {\n+    copyfrom_source,\n+    copyfrom_target,\n+    copyfrom_new\n+};\n+\n+struct svndiff_instruction\n+{\n+  enum svndiff_action action_code;\n+  size_t offset;\n+  size_t length;\n+};\n+\n+/* An svndiff_window object describes how to reconstruct a\n+ * contiguous section of the target string (the \"target view\") using a\n+ * specified contiguous region of the source string (the \"source\n+ * view\").  It contains a series of instructions which assemble the\n+ * new target string text by pulling together substrings from:\n+ *\n+ *   - the source view,\n+ *\n+ *   - the previously constructed portion of the target view,\n+ *\n+ *   - a string of new data contained within the window structure\n+ *\n+ * The source view must always slide forward from one window to the\n+ * next; that is, neither the beginning nor the end of the source view\n+ * may move to the left as we read from a window stream.  This\n+ * property allows us to apply deltas to non-seekable source streams\n+ * without making a full copy of the source stream.\n+ */\n+struct svndiff_window\n+{\n+  size_t sview_offset;\n+  size_t sview_len;\n+  size_t tview_len;\n+  size_t ins_len;\n+  size_t newdata_len;\n+  struct svndiff_instruction *ops;\n+  char *newdata;\n+};\n-- \n1.7.2.2.409.gdbb11.dirty\n"},{"id":"149833","messageId":"1283601539-4477-1-git-send-email-david.barr@cordelta.com","threadId":"24968","inReplyTo":"1283586214-31204-2-git-send-email-artagnon@gmail.com","subject":"[PATCH] vcs-svn: Avoid %z in format string","fromName":"David Barr","fromEmail":"david.barr@cordelta.com","sentAt":"2010-09-04T11:58:59Z","receivedAt":"2010-09-04T11:58:59Z","isPatch":true,"sender":{"key":"david.barr@cordelta.com","avatar":"https://avatars.githubusercontent.com/u/220594?v=4"},"body":"In the spirit of v1.6.4-rc0~124 (MinGW: Fix compiler warning in\nmerge-recursive, 2009-05-23), use a 64-bit integer instead.\n---\n vcs-svn/svndiff.c |   41 +++++++++++++++++++++++++----------------\n 1 files changed, 25 insertions(+), 16 deletions(-)\n\ndiff --git a/vcs-svn/svndiff.c b/vcs-svn/svndiff.c\nindex 901fc1a..12b7459 100644\n--- a/vcs-svn/svndiff.c\n+++ b/vcs-svn/svndiff.c\n@@ -57,7 +57,7 @@ size_t read_one_instruction(struct svndiff_instruction *op)\n \t   bits; the remaining 6 bits may contain the length */\n \taction = (c >> 6) & 3;\n \tif (action >= 3)\n-\t\tdie(\"Invalid instruction %d\", action);\n+\t\tdie(\"Invalid instruction %\"PRIu64, (uint64_t) action);\n \n \top->action_code = (enum svndiff_action)(action);\n \n@@ -93,39 +93,44 @@ size_t read_instructions(struct svndiff_window *window, size_t *ninst)\n \t\tbsize += read_one_instruction(op);\n \n \t\tif (DEBUG)\n-\t\t\tfprintf(stderr, \"Instruction: %d %d %d (%d)\\n\",\n-\t\t\t\top->action_code, op->length, op->offset, bsize);\n+\t\t\tfprintf(stderr,\n+\t\t\t\t\"Instruction: %\"PRIu64\" %\"PRIu64\" %\"PRIu64\" (%\"PRIu64\")\\n\",\n+\t\t\t\t(uint64_t) op->action_code,\n+\t\t\t\t(uint64_t) op->length,\n+\t\t\t\t(uint64_t) op->offset,\n+\t\t\t\t(uint64_t) bsize);\n \n \t\tif (op == NULL)\n \t\t\tdie(\"Invalid diff stream: \"\n-\t\t\t    \"instruction %d cannot be decoded\", *ninst);\n+\t\t\t\t\"instruction %\"PRIu64\" cannot be decoded\", (uint64_t) *ninst);\n \t\telse if (op->length == 0)\n \t\t\tdie(\"Invalid diff stream: \"\n-\t\t\t    \"instruction %d has length zero\", *ninst);\n+\t\t\t\t\"instruction %\"PRIu64\" has length zero\", (uint64_t) *ninst);\n \t\telse if (op->length > window->tview_len - tpos)\n \t\t\tdie(\"Invalid diff stream: \"\n-\t\t\t    \"instruction %d overflows the target view\", *ninst);\n+\t\t\t\t\"instruction %\"PRIu64\" overflows the target view\",\n+\t\t\t(uint64_t) *ninst);\n \n \t\tswitch (op->action_code)\n \t\t{\n \t\tcase copyfrom_source:\n \t\t\tif (op->length > window->sview_len - op->offset ||\n-\t\t\t    op->offset > window->sview_len)\n+\t\t\t\top->offset > window->sview_len)\n \t\t\t\tdie(\"Invalid diff stream: \"\n-\t\t\t\t    \"[src] instruction %d overflows \"\n-\t\t\t\t    \" the source view\", *ninst);\n+\t\t\t\t\t\"[src] instruction %\"PRIu64\" overflows \"\n+\t\t\t\t\t\" the source view\", (uint64_t) *ninst);\n \t\t\tbreak;\n \t\tcase copyfrom_target:\n \t\t\tif (op->offset >= tpos)\n \t\t\t\tdie(\"Invalid diff stream: \"\n-\t\t\t\t    \"[tgt] instruction %d starts \"\n-\t\t\t\t    \"beyond the target view position\", *ninst);\n+\t\t\t\t\t\"[tgt] instruction %\"PRIu64\" starts \"\n+\t\t\t\t\t\"beyond the target view position\", (uint64_t) *ninst);\n \t\t\tbreak;\n \t\tcase copyfrom_new:\n \t\t\tif (op->length > window->newdata_len - npos)\n \t\t\t\tdie(\"Invalid diff stream: \"\n-\t\t\t\t    \"[new] instruction %d overflows \"\n-\t\t\t\t    \"the new data section\", *ninst);\n+\t\t\t\t\t\"[new] instruction %\"PRIu64\" overflows \"\n+\t\t\t\t\t\"the new data section\", (uint64_t) *ninst);\n \t\t\tnpos += op->length;\n \t\t\tbreak;\n \t\t}\n@@ -163,9 +168,13 @@ size_t read_window_header(struct svndiff_window *window)\n \t\tdie(\"Svndiff contains corrupt window header\");\n \n \tif (DEBUG)\n-\t\tfprintf(stderr, \"Window header: %d %d %d %d %d\\n\",\n-\t\t\twindow->sview_offset, window->sview_len,\n-\t\t\twindow->tview_len, window->ins_len, window->newdata_len);\n+\t\tfprintf(stderr,\n+\t\t\t\"Window header: %\"PRIu64\" %\"PRIu64\" %\"PRIu64\" %\"PRIu64\" %\"PRIu64\"\\n\",\n+\t\t\t(uint64_t) window->sview_offset,\n+\t\t\t(uint64_t) window->sview_len,\n+\t\t\t(uint64_t) window->tview_len,\n+\t\t\t(uint64_t) window->ins_len,\n+\t\t\t(uint64_t) window->newdata_len);\n \treturn bsize;\n }\n \n-- \n1.7.2.2\n"},{"id":"149834","messageId":"20100904122053.GC32397@kytes","threadId":"24968","inReplyTo":"1283601539-4477-1-git-send-email-david.barr@cordelta.com","subject":"Re: [PATCH] vcs-svn: Avoid %z in format string","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2010-09-04T12:20:56Z","receivedAt":"2010-09-04T12:20:56Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Hi David,\n\nDavid Barr writes:\n> In the spirit of v1.6.4-rc0~124 (MinGW: Fix compiler warning in\n> merge-recursive, 2009-05-23), use a 64-bit integer instead.\n\nSquashed. Thanks much :)\n\n-- Ram\n"}]}