From 98d1e8272cc62bcb93bc17e9a1f8315a92134ac9 Mon Sep 17 00:00:00 2001 From: molsonkiko <46202915+molsonkiko@users.noreply.github.com> Date: Mon, 11 Dec 2023 16:20:02 -0800 Subject: [PATCH] add doc type button to tree; set() RemesPath func - new document type button above json tree makes it easier to change how the document is interpreted (e.g., go from JSON mode to REGEX mode or vice versa) - new set() non-vectorized function in RemesPath --- CHANGELOG.md | 18 +- .../Forms/TreeViewer.Designer.cs | 62 +- JsonToolsNppPlugin/Forms/TreeViewer.cs | 56 +- JsonToolsNppPlugin/Forms/TreeViewer.resx | 2 +- JsonToolsNppPlugin/JSONTools/JNode.cs | 2 +- JsonToolsNppPlugin/JSONTools/RemesPath.cs | 4 +- .../JSONTools/RemesPathFunctions.cs | 42 +- JsonToolsNppPlugin/Main.cs | 31 +- JsonToolsNppPlugin/Properties/AssemblyInfo.cs | 4 +- JsonToolsNppPlugin/Tests/RemesPathTests.cs | 1 + JsonToolsNppPlugin/Tests/TestRunner.cs | 2 +- .../Tests/UserInterfaceTests.cs | 929 +++++++++--------- docs/README.md | 20 + docs/RemesPath.md | 28 +- .../document type box example - JSON mode.PNG | Bin 0 -> 12741 bytes ...document type box example - JSONL mode.PNG | Bin 0 -> 15228 bytes ...document type box example - REGEX mode.PNG | Bin 0 -> 11702 bytes most recent errors.txt | 88 +- 18 files changed, 749 insertions(+), 540 deletions(-) create mode 100644 docs/document type box example - JSON mode.PNG create mode 100644 docs/document type box example - JSONL mode.PNG create mode 100644 docs/document type box example - REGEX mode.PNG diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ba5eaa..f901b33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - this problem only seems to appear after the user has opened a docking form, and maybe not even every time - bug with calling arg functions on projections - seems like object projections are treated as arrays when calling arg functions on them in some cases? - issue with treeview closing when a file with a treeview is moved from one view to another -- `loop()` function used in `s_sub` callbacks is not thread-safe. This doesn't matter right now because RemesPath is single-threaded, but it could matter in the future. +- `loop()` function used in `s_sub` callbacks is not thread-safe. *This doesn't matter right now* because RemesPath is single-threaded, but it could matter in the future. - __Known issues with `Select This` and `Select all children` commands in tree view (FIX THESE ASAP)__: * Empty strings are ignored by `Select this` * Floating point numbers may not be selected correctly (JSON string may have different length from the number representation in the CSV) @@ -49,13 +49,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). 1. Option to customize which [toolbar icons](/docs/README.md#toolbar-icons) are displayed, and their order. 2. New [regex search form](/docs/README.md#regex-search-form) for using treeview to see regex search results in any file -3. [For loops in RemesPath](/docs/RemesPath.md#for-loopsloop-variables-added-in-v60) -4. [`bool`, `s_csv` and `s_fa` vectorized arg functions](/docs/RemesPath.md#vectorized-functions) and [`randint`, `csv_regex`, and `to_csv` non-vectorized arg functions](/docs/RemesPath.md#non-vectorized-functions) to RemesPath. -5. Make second argument of [`s_split` RemesPath function](/docs/RemesPath.md#vectorized-functions) optional; 1-argument variant splits on whitespace. -6. Right-click dropdown menu in [error form](/docs/README.md#error-form-and-status-bar), allowing export of errors to JSON or refreshing the form. -7. The parser is now much better at recovering when an object is missing its closing `'}'` or an array is missing its closing `']'`. -8. Support for [JSON Schema validation](/docs/README.md#validating-json-against-json-schema) of `enum` keyword where the `type` is missing or an array. -9. `Ctrl+Up` now snaps to parent of currently selected node in tree view. `Ctrl+Down` now snaps to the last direct child of the currently selected node. +3. New [document type list box in tree view](/docs/README.md#document-type-box-added-in-v60) +4. [For loops in RemesPath](/docs/RemesPath.md#for-loopsloop-variables-added-in-v60) +5. [`bool`, `s_csv` and `s_fa` RemesPath vectorized arg functions](/docs/RemesPath.md#vectorized-functions) +6. [`randint`, `csv_regex`, `set`, and `to_csv` RemesPath non-vectorized arg functions](/docs/RemesPath.md#non-vectorized-functions) +7. Make second argument of [`s_split` RemesPath function](/docs/RemesPath.md#vectorized-functions) optional; 1-argument variant splits on whitespace. +8. Right-click dropdown menu in [error form](/docs/README.md#error-form-and-status-bar), allowing export of errors to JSON or refreshing the form. +9. The parser is now much better at recovering when an object is missing its closing `'}'` or an array is missing its closing `']'`. +10. Support for [JSON Schema validation](/docs/README.md#validating-json-against-json-schema) of `enum` keyword where the `type` is missing or an array. +11. `Ctrl+Up` now snaps to parent of currently selected node in tree view. `Ctrl+Down` now snaps to the last direct child of the currently selected node. ### Changed diff --git a/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs b/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs index b36b371..a8e4a2c 100644 --- a/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs +++ b/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs @@ -60,12 +60,13 @@ private void InitializeComponent() this.PythonStylePathItem = new System.Windows.Forms.ToolStripMenuItem(); this.RemesPathStylePathItem = new System.Windows.Forms.ToolStripMenuItem(); this.ToggleSubtreesItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SelectThisItem = new System.Windows.Forms.ToolStripMenuItem(); this.OpenSortFormItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SelectAllChildrenItem = new System.Windows.Forms.ToolStripMenuItem(); this.CurrentPathBox = new System.Windows.Forms.TextBox(); this.RefreshButton = new System.Windows.Forms.Button(); this.FindReplaceButton = new System.Windows.Forms.Button(); - this.SelectThisItem = new System.Windows.Forms.ToolStripMenuItem(); - this.SelectAllChildrenItem = new System.Windows.Forms.ToolStripMenuItem(); + this.DocumentTypeListBox = new System.Windows.Forms.ListBox(); this.NodeRightClickMenu.SuspendLayout(); this.SuspendLayout(); // @@ -74,10 +75,10 @@ private void InitializeComponent() this.Tree.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.Tree.Location = new System.Drawing.Point(4, 84); + this.Tree.Location = new System.Drawing.Point(4, 99); this.Tree.Name = "Tree"; - this.Tree.Size = new System.Drawing.Size(457, 346); - this.Tree.TabIndex = 6; + this.Tree.Size = new System.Drawing.Size(457, 331); + this.Tree.TabIndex = 8; this.Tree.BeforeExpand += new System.Windows.Forms.TreeViewCancelEventHandler(this.Tree_BeforeExpand); this.Tree.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.Tree_AfterSelect); this.Tree.NodeMouseClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.Tree_NodeMouseClick); @@ -105,7 +106,7 @@ private void InitializeComponent() this.QueryBox.Location = new System.Drawing.Point(4, 4); this.QueryBox.Multiline = true; this.QueryBox.Name = "QueryBox"; - this.QueryBox.Size = new System.Drawing.Size(203, 74); + this.QueryBox.Size = new System.Drawing.Size(203, 89); this.QueryBox.TabIndex = 0; this.QueryBox.Text = "@"; this.QueryBox.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.QueryBox_KeyPress); @@ -163,7 +164,7 @@ private void InitializeComponent() this.OpenSortFormItem, this.SelectAllChildrenItem}); this.NodeRightClickMenu.Name = "NodeRightClickMenu"; - this.NodeRightClickMenu.Size = new System.Drawing.Size(268, 200); + this.NodeRightClickMenu.Size = new System.Drawing.Size(268, 172); // // CopyValueMenuItem // @@ -233,6 +234,12 @@ private void InitializeComponent() this.ToggleSubtreesItem.Size = new System.Drawing.Size(267, 24); this.ToggleSubtreesItem.Text = "Expand/collapse all subtrees"; // + // SelectThisItem + // + this.SelectThisItem.Name = "SelectThisItem"; + this.SelectThisItem.Size = new System.Drawing.Size(267, 24); + this.SelectThisItem.Text = "Select this"; + // // OpenSortFormItem // this.OpenSortFormItem.Name = "OpenSortFormItem"; @@ -240,6 +247,13 @@ private void InitializeComponent() this.OpenSortFormItem.Text = "Sort array..."; this.OpenSortFormItem.Visible = false; // + // SelectAllChildrenItem + // + this.SelectAllChildrenItem.Name = "SelectAllChildrenItem"; + this.SelectAllChildrenItem.Size = new System.Drawing.Size(267, 24); + this.SelectAllChildrenItem.Text = "Select all children"; + this.SelectAllChildrenItem.Visible = false; + // // CurrentPathBox // this.CurrentPathBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) @@ -248,7 +262,7 @@ private void InitializeComponent() this.CurrentPathBox.Name = "CurrentPathBox"; this.CurrentPathBox.ReadOnly = true; this.CurrentPathBox.Size = new System.Drawing.Size(337, 22); - this.CurrentPathBox.TabIndex = 8; + this.CurrentPathBox.TabIndex = 10; this.CurrentPathBox.TabStop = false; // // RefreshButton @@ -270,30 +284,35 @@ private void InitializeComponent() this.FindReplaceButton.Location = new System.Drawing.Point(4, 435); this.FindReplaceButton.Name = "FindReplaceButton"; this.FindReplaceButton.Size = new System.Drawing.Size(114, 23); - this.FindReplaceButton.TabIndex = 7; + this.FindReplaceButton.TabIndex = 9; this.FindReplaceButton.Text = "Find/replace"; this.FindReplaceButton.UseVisualStyleBackColor = true; this.FindReplaceButton.Click += new System.EventHandler(this.FindReplaceButton_Click); this.FindReplaceButton.KeyUp += new System.Windows.Forms.KeyEventHandler(this.TreeViewer_KeyUp); // - // SelectThisItem - // - this.SelectThisItem.Name = "SelectThisItem"; - this.SelectThisItem.Size = new System.Drawing.Size(267, 24); - this.SelectThisItem.Text = "Select this"; - // - // SelectAllChildrenItem - // - this.SelectAllChildrenItem.Name = "SelectAllChildrenItem"; - this.SelectAllChildrenItem.Size = new System.Drawing.Size(267, 24); - this.SelectAllChildrenItem.Text = "Select all children"; - this.SelectAllChildrenItem.Visible = false; + // DocumentTypeListBox + // + this.DocumentTypeListBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.DocumentTypeListBox.FormattingEnabled = true; + this.DocumentTypeListBox.ItemHeight = 16; + this.DocumentTypeListBox.Items.AddRange(new object[] { + "JSON mode", + "JSONL mode", + "INI mode", + "REGEX mode"}); + this.DocumentTypeListBox.Location = new System.Drawing.Point(220, 69); + this.DocumentTypeListBox.Name = "DocumentTypeListBox"; + this.DocumentTypeListBox.Size = new System.Drawing.Size(130, 20); + this.DocumentTypeListBox.TabIndex = 6; + this.DocumentTypeListBox.SelectedIndexChanged += new System.EventHandler(this.DocumentTypeListBox_SelectedIndexChanged); + this.DocumentTypeListBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.TreeViewer_KeyUp); // // TreeViewer // this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(471, 461); + this.Controls.Add(this.DocumentTypeListBox); this.Controls.Add(this.FindReplaceButton); this.Controls.Add(this.RefreshButton); this.Controls.Add(this.CurrentPathBox); @@ -337,5 +356,6 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem OpenSortFormItem; private System.Windows.Forms.ToolStripMenuItem SelectThisItem; private System.Windows.Forms.ToolStripMenuItem SelectAllChildrenItem; + private System.Windows.Forms.ListBox DocumentTypeListBox; } } \ No newline at end of file diff --git a/JsonToolsNppPlugin/Forms/TreeViewer.cs b/JsonToolsNppPlugin/Forms/TreeViewer.cs index a626f3d..c92a22c 100644 --- a/JsonToolsNppPlugin/Forms/TreeViewer.cs +++ b/JsonToolsNppPlugin/Forms/TreeViewer.cs @@ -51,6 +51,11 @@ public partial class TreeViewer : Form /// private bool isExpandingAllSubtrees; + /// + /// avoid unnecessary parsing if a change in the selected index of the DocumentTypeComboBox was not performed manually (in which case this is true) + /// + public bool documentTypeIndexChangeWasAutomatic; + /// /// If the user performs an undo or redo action, /// this will be set to true so that the next time the user performs a RemesPath query, @@ -90,6 +95,9 @@ public TreeViewer(JNode json) findReplaceForm = null; csvDelim = '\x00'; csvQuote = '\x00'; + documentTypeIndexChangeWasAutomatic = true; // avoid parsing twice on initialization + SetDocumentTypeListBoxIndex(GetDocumentType()); + documentTypeIndexChangeWasAutomatic = false; FormStyle.ApplyStyle(this, Main.settings.use_npp_styling); } @@ -149,7 +157,8 @@ private void TreeViewer_KeyUp(object sender, KeyEventArgs e) else if (e.KeyCode == Keys.Tab) { Control next = GetNextControl((Control)sender, !e.Shift); - while ((next == null) || (!next.TabStop)) next = GetNextControl(next, !e.Shift); + while (next is null || !next.TabStop) + next = GetNextControl(next, !e.Shift); next.Focus(); } else if (sender is TreeView && e.Control) @@ -158,6 +167,8 @@ private void TreeViewer_KeyUp(object sender, KeyEventArgs e) if (e.KeyCode == Keys.Up) { TreeNode selected = Tree.SelectedNode; + if (selected is null) + return; TreeNode parent = selected.Parent; if (parent is null) return; @@ -167,7 +178,7 @@ private void TreeViewer_KeyUp(object sender, KeyEventArgs e) else if (e.KeyCode == Keys.Down) { TreeNode selected = Tree.SelectedNode; - if (selected.Nodes.Count > 0) + if (!(selected is null) && selected.Nodes.Count > 0) { if (!selected.IsExpanded) selected.Expand(); @@ -1149,7 +1160,7 @@ private void RefreshButton_Click(object sender, EventArgs e) { shouldRefresh = false; string cur_fname = Npp.notepad.GetCurrentFilePath(); - (bool _, JNode new_json, bool _, DocumentType _) = Main.TryParseJson(); + (bool _, JNode new_json, bool _, DocumentType _) = Main.TryParseJson(GetDocumentTypeFromListBox()); if (new_json == null) return; fname = cur_fname; @@ -1173,6 +1184,45 @@ private void TreeViewer_DoubleClick(object sender, EventArgs e) Npp.notepad.OpenFile(fname); } + public void SetDocumentTypeListBoxIndex(DocumentType documentType) + { + switch (documentType) + { + case DocumentType.NONE: + case DocumentType.JSON: + DocumentTypeListBox.SelectedIndex = 0; + break; + case DocumentType.JSONL: DocumentTypeListBox.SelectedIndex = 1; break; + case DocumentType.INI: DocumentTypeListBox.SelectedIndex = 2; break; + case DocumentType.REGEX: DocumentTypeListBox.SelectedIndex = 3; break; + default: break; + } + } + + public DocumentType GetDocumentTypeFromListBox() + { + switch (DocumentTypeListBox.SelectedIndex) + { + case 0: return DocumentType.JSON; + case 1: return DocumentType.JSONL; + case 2: return DocumentType.INI; + case 3: return DocumentType.REGEX; + default: return DocumentType.NONE; + } + } + + private void DocumentTypeListBox_SelectedIndexChanged(object sender, EventArgs e) + { + if (documentTypeIndexChangeWasAutomatic) + return; + DocumentType newDocumentType = GetDocumentTypeFromListBox(); + DocumentType oldDocumentType = GetDocumentType(); + if (oldDocumentType == newDocumentType) + return; + (_, JNode newJson, _, _) =Main.TryParseJson(newDocumentType); + JsonTreePopulate(newJson); + } + /// /// Just the filename, no directory information.

/// If no fname supplied, gets the relative filename for this TreeViewer's fname. diff --git a/JsonToolsNppPlugin/Forms/TreeViewer.resx b/JsonToolsNppPlugin/Forms/TreeViewer.resx index 5439864..bc10a73 100644 --- a/JsonToolsNppPlugin/Forms/TreeViewer.resx +++ b/JsonToolsNppPlugin/Forms/TreeViewer.resx @@ -125,7 +125,7 @@ AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAADC - CgAAAk1TRnQBSQFMAgEBCAEAAZgBAQGYAQEBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo + CgAAAk1TRnQBSQFMAgEBCAEAAbABAQGwAQEBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo AwABQAMAATADAAEBAQABCAYAAQwYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5 AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA diff --git a/JsonToolsNppPlugin/JSONTools/JNode.cs b/JsonToolsNppPlugin/JSONTools/JNode.cs index b8f2374..2e6e207 100644 --- a/JsonToolsNppPlugin/JSONTools/JNode.cs +++ b/JsonToolsNppPlugin/JSONTools/JNode.cs @@ -1977,6 +1977,7 @@ public JNode GetQuery() /// public JNode Evaluate(JNode inp) { + ArgFunction.InitializeGlobals(mutatesInput); JNode lastStatement = EvaluateStatementsFromStartToEnd(inp, 0, statements.Count); Reset(); return lastStatement; @@ -2062,7 +2063,6 @@ private JNode EvaluateStatementsFromStartToEnd(JNode inp, int start, int end) { JNode lastStatement = null; indexInStatements = start; - ArgFunction.regexSearchResultsShouldBeCached = !mutatesInput; while (indexInStatements < end) { JNode statement = statements[indexInStatements]; diff --git a/JsonToolsNppPlugin/JSONTools/RemesPath.cs b/JsonToolsNppPlugin/JSONTools/RemesPath.cs index 575c995..66266e4 100644 --- a/JsonToolsNppPlugin/JSONTools/RemesPath.cs +++ b/JsonToolsNppPlugin/JSONTools/RemesPath.cs @@ -1198,8 +1198,6 @@ private JNode ParseQuery(List toks) int pos = 0; int end = toks.Count; var context = new JQueryContext(); - ArgFunction.csvDelimiterInLastQuery = '\x00'; - ArgFunction.csvQuoteCharInLastQuery = '\x00'; Dictionary isVarnameFunctionOfInput = DetermineWhichVariablesAreFunctionsOfInput(toks); while (pos < end) { @@ -1243,7 +1241,7 @@ private Dictionary DetermineWhichVariablesAreFunctionsOfInput(List else pos = endOfStatement + 1; } - ArgFunction.regexSearchResultsShouldBeCached = !containsMutation; // any mutation could potentially mutate values inside the cache, which is very bad + ArgFunction.InitializeGlobals(containsMutation); if (containsMutation) { // no variable is safe from the mutation, not even ones that were declared before the mutation expression diff --git a/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs b/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs index 4af4da1..054a54e 100644 --- a/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs +++ b/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs @@ -1084,6 +1084,21 @@ public void PadToMaxArgs(List args) } } + /// + /// there are currently three mutable public global variables that are set in queries:

+ /// regexSearchResultsShouldBeCached, csvDelimiterInLastQuery, and csvQuoteCharInLastQuery.

+ /// These all relate to s_csv and s_fa.

+ /// This is basically a hack, because RemesPath does not currently support deep top-down introspection of a query's AST + /// to determine if s_csv or s_fa has been called in a certain way. + ///
+ /// + public static void InitializeGlobals(bool containsMutation) + { + regexSearchResultsShouldBeCached = !containsMutation; + csvDelimiterInLastQuery = '\x00'; + csvQuoteCharInLastQuery = '\x00'; + } + #region NON_VECTORIZED_ARG_FUNCTIONS /// @@ -1691,6 +1706,28 @@ public static JNode Items(List args) return new JArray(0, its); } + /// + /// set(x: array) -> object

+ /// returns an object where the keys are all the stringified unique values in x and the values are all null. + ///
+ public static JNode Set(List args) + { + List arr = ((JArray)args[0]).children; + var set = new Dictionary(); + foreach (JNode child in arr) + { + var val = child.value; + string key = val is string s ? JNode.StrToString(s, false) : child.ToString(); + set[key] = new JNode(); + } + return new JObject(0, set); + } + + /// + /// unique(x: array, is_sorted: bool=false) -> array

+ /// returns an array of all the distinct values in x (they must all be scalars).

+ /// If is_sorted, the returned array is sorted. Otherwise the order is random. + ///
public static JNode Unique(List args) { var itbl = (JArray)args[0]; @@ -1698,6 +1735,8 @@ public static JNode Unique(List args) var uniq = new HashSet(); foreach (JNode val in itbl.children) { + if ((val.type & Dtype.SCALAR) == 0) + throw new RemesPathArgumentException("First argument to unique must be an array of all scalars.", 0, FUNCTIONS["unique"]); uniq.Add(val.value); } var uniq_list = new List(); @@ -2347,7 +2386,7 @@ public static JNode StrFind(List args) private static readonly int MAX_DOC_SIZE_CACHE_REGEX_SEARCH = IntPtr.Size * 1_250_000; /// - /// do not cache for any reason. This is usually because the query involves mutation and there is some danger of mutating a value in the cache. + /// if false, do not cache for any reason. This is usually because the query involves mutation and there is some danger of mutating a value in the cache. /// public static bool regexSearchResultsShouldBeCached = true; @@ -3306,6 +3345,7 @@ public static JNode ObjectsToJNode(object obj) ["randint"] = new ArgFunction(RandomInteger, "randint", Dtype.INT, 1, 2, false, new Dtype[] {Dtype.INT, Dtype.INT}, false), ["range"] = new ArgFunction(Range, "range", Dtype.ARR, 1, 3, false, new Dtype[] {Dtype.INT, Dtype.INT, Dtype.INT}), ["s_join"] = new ArgFunction(StringJoin, "s_join", Dtype.STR, 2, 2, false, new Dtype[] {Dtype.STR, Dtype.ARR}), + ["set"] = new ArgFunction(Set, "set", Dtype.OBJ, 1, 1, false, new Dtype[] {Dtype.ARR}), ["sort_by"] = new ArgFunction(SortBy, "sort_by", Dtype.ARR, 2, 3, false, new Dtype[] { Dtype.ARR, Dtype.STR | Dtype.INT | Dtype.FUNCTION, Dtype.BOOL }), ["sorted"] = new ArgFunction(Sorted, "sorted", Dtype.ARR, 1, 2, false, new Dtype[] {Dtype.ARR, Dtype.BOOL}), ["stringify"] = new ArgFunction(Stringify, "stringify", Dtype.STR, 1, 1, false, new Dtype[] { Dtype.ANYTHING }), diff --git a/JsonToolsNppPlugin/Main.cs b/JsonToolsNppPlugin/Main.cs index fc5c307..9490795 100644 --- a/JsonToolsNppPlugin/Main.cs +++ b/JsonToolsNppPlugin/Main.cs @@ -510,8 +510,7 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen if (wasAutotriggered && len > sizeThreshold) return (false, null, false, DocumentType.NONE); string text = Npp.editor.GetText(len + 1); - if (documentType == DocumentType.JSON || documentType == DocumentType.NONE || // if user didn't specify how to parse the document - (documentType == DocumentType.REGEX && (info == null || info.json == null || info.json.type == Dtype.NULL))) // or user specified REGEX and document was not already parsed + if (documentType == DocumentType.NONE) // if user didn't specify how to parse the document { // 1. check if the document has a file extension with an associated DocumentType string fileExtension = Npp.FileExtension().ToLower(); @@ -520,7 +519,7 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen documentType = docTypeFromExtension; // 2. check if the user previously chose a document type, and use that if so. // this overrides the default for the file extension. - if (previouslyChosenDocType != DocumentType.NONE && documentType != DocumentType.REGEX) + if (previouslyChosenDocType != DocumentType.NONE) documentType = previouslyChosenDocType; } if (documentType == DocumentType.INI) @@ -574,13 +573,21 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen foreach ((int start, int end) in selRanges) { string selRange = Npp.GetSlice(start, end); - JNode subJson = documentType == DocumentType.REGEX ? new JNode(selRange, start) : jsonParser.Parse(selRange); + JNode subJson; + if (documentType == DocumentType.REGEX) + { + subJson = new JNode(selRange, 0); + } + else + { + subJson = jsonParser.Parse(selRange); + lints.AddRange(jsonParser.lint); + fatalErrors.Add(jsonParser.fatal); + if (errorMessage == null) + errorMessage = jsonParser.fatalError?.ToString(); + } string key = $"{start},{end}"; obj[key] = subJson; - lints.AddRange(jsonParser.lint); - fatalErrors.Add(jsonParser.fatal); - if (errorMessage == null) - errorMessage = jsonParser.fatalError?.ToString(); } } if (!isRecursion && oneSelRange && SelectionManager.NoSelectionIsValidJson(json, !noTextSelected, fatalErrors)) @@ -627,7 +634,8 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen Npp.notepad.SetStatusBarSection($"JSON with fatal errors - {lintCount} errors (Alt-P-J-E to view)", StatusBarSection.DocType); } - else if (!(!info.usesSelections && documentType != DocumentType.JSON && documentType != DocumentType.JSONL)) // only touch status bar if whole doc is parsed as JSON or JSON Lines + else if (jsonParser.state < ParserState.FATAL && !(!info.usesSelections && documentType != DocumentType.JSON && documentType != DocumentType.JSONL)) + // only touch status bar if whole doc is parsed as JSON or JSON Lines, and there are no fatal errors { string doctypeDescription; switch (jsonParser.state) @@ -648,7 +656,12 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen Npp.notepad.SetStatusBarSection(doctypeStatusBarEntry, StatusBarSection.DocType); } if (info.tv != null) + { info.tv.json = json; + info.tv.documentTypeIndexChangeWasAutomatic = true; + info.tv.SetDocumentTypeListBoxIndex(documentType); + info.tv.documentTypeIndexChangeWasAutomatic = false; + } info.comments = comments; jsonFileInfos[activeFname] = info; return (fatal, json, info.usesSelections, documentType); diff --git a/JsonToolsNppPlugin/Properties/AssemblyInfo.cs b/JsonToolsNppPlugin/Properties/AssemblyInfo.cs index f0397d3..a8a7a82 100644 --- a/JsonToolsNppPlugin/Properties/AssemblyInfo.cs +++ b/JsonToolsNppPlugin/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion("5.8.0.16")] -[assembly: AssemblyFileVersion("5.8.0.16")] +[assembly: AssemblyVersion("5.8.0.17")] +[assembly: AssemblyFileVersion("5.8.0.17")] diff --git a/JsonToolsNppPlugin/Tests/RemesPathTests.cs b/JsonToolsNppPlugin/Tests/RemesPathTests.cs index 2b8fd4a..ab05e2d 100644 --- a/JsonToolsNppPlugin/Tests/RemesPathTests.cs +++ b/JsonToolsNppPlugin/Tests/RemesPathTests.cs @@ -488,6 +488,7 @@ public static bool Test() new Query_DesiredResult("to_csv(@.foo[1], `.`)", "\"\\\"3.0\\\"\\r\\n\\\"4.0\\\"\\r\\n\\\"5.0\\\"\""), new Query_DesiredResult("to_csv(@.foo[:][0],,`\\n`)", "\"0\\n3.0\\n6.0\""), new Query_DesiredResult("to_csv(@.foo[:]{a: @[1]})", "\"a\\r\\n1\\r\\n4.0\\r\\n7.0\""), + new Query_DesiredResult("set(concat(@.foo[0], @.foo[1][:1], j`[\"a\", \"a\"]`))", "{\"0\": null, \"1\": null, \"2\": null, \"3.0\": null, \"a\": null}"), // ===================== s_csv CSV parser ======================== // 3-column 14 rows, ',' delimiter, CRLF newline, '"' quote character, newline before EOF new Query_DesiredResult("s_csv(`nums,names,cities\\r\\n" + diff --git a/JsonToolsNppPlugin/Tests/TestRunner.cs b/JsonToolsNppPlugin/Tests/TestRunner.cs index c203e1e..b17eebc 100644 --- a/JsonToolsNppPlugin/Tests/TestRunner.cs +++ b/JsonToolsNppPlugin/Tests/TestRunner.cs @@ -42,7 +42,7 @@ public static async Task RunAll() (RemesParserTester.Test, "RemesPath parser and compiler", false, false), (RemesPathThrowsWhenAppropriateTester.Test, "RemesPath throws errors on bad inputs", false, false), (RemesPathAssignmentTester.Test, "RemesPath assignment operations", false, false), - (() => RemesPathFuzzTester.Test(10000, 20), fuzzTestName, false, false), + (() => RemesPathFuzzTester.Test(5000, 20), fuzzTestName, false, false), (RemesPathComplexQueryTester.Test, "multi-statement queries in RemesPath", false, false), (JsonSchemaMakerTester.Test, "JsonSchema generator", false, false), diff --git a/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs b/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs index e734484..8635f87 100644 --- a/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs +++ b/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs @@ -15,9 +15,6 @@ public class UserInterfaceTester private static int lowestFilenameNumberNotUsed = 0; - // later on tests will be added in a method; we don't want to add them every time the tests are run, only the first time - public static bool hasRunTestsThisSession = false; - public static string lastClipboardValue = null; private static RemesParser remesParser = new RemesParser(); @@ -279,430 +276,484 @@ public static bool ExecuteFileManipulation(string command, List messages messages.Add(regexSettingsErrorMsg); Main.regexSearchForm.SearchButton.PerformClick(); break; + case "set_document_type": + string newDocumentTypeName = (string)args[0]; + if (!hasTreeView) + { + messages.Add($"FAIL: Wanted to set the document type to {newDocumentTypeName} with the treeview's combo box, but the treeview wasn't open"); + return true; + } + switch (newDocumentTypeName) + { + case "NONE": + case "JSON": + Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.JSON); + break; + case "JSONL": Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.JSONL); break; + case "INI": Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.INI); break; + case "REGEX": Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.REGEX); break; + } + messages.Add($"Set document type to {newDocumentTypeName} with the treeview's combo box"); + break; default: throw new ArgumentException($"Unrecognized command {command}"); } return false; } - public static List<(string command, object[] args)> testcases = new List<(string command, object[] args)> + public static bool Test() { - ("overwrite", new object[]{"[1, 2, false]\r\n{\"a\": 3, \"b\": 1.5}\r\n[{\"c\": [null]}, -7]"}), // first line: 0 to 13, second line: 15 to 33, third line: 35 to 54 - ("select", new object[]{new string[]{ "0,13", "15,33", "35,54" } }), - // when pretty-printed in PPrint style looks like this: - //[ - // 1, - // 2, - // false - //] - //{ - // "a": 3, - // "b": 1.5 - //} - //[ - // {"c": [null]}, - // -7 - //] - // first JSON: 0 to 31, second JSON: 33 to 64, third JSON: 66 to 98 - ("pretty_print", new object[]{ }), - ("compare_text", new object[]{ "[\r\n 1,\r\n 2,\r\n false\r\n]\r\n{\r\n \"a\": 3,\r\n \"b\": 1.5\r\n}\r\n[\r\n {\"c\": [null]},\r\n -7\r\n]"}), - ("compare_selections", new object[]{new string[]{"0,31", "33,64", "66,98"} }), - // when compressed looks like this: - //[1,2,false] - //{"a":3,"b":1.5} - //[{"c":[null]},-7] - // first JSON: 0 to 11, second JSON: 13 to 28, third JSON: 30 to 47 - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,false]\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), - ("compare_selections", new object[]{new string[] {"0,11", "13,28", "30,47"} }), - // TEST THAT TREENODE CLICKS GO TO RIGHT LOCATION - ("tree_open", new object[]{}), - ("treenode_click", new object[]{ new string[] { "0,11 : [3]", "2 : false"} }), - ("compare_selections", new object[]{new string[] {"5,5"} }), // in front of @.`0,11`[2] - ("treenode_click", new object[]{ new string[] {"30,47 : [2]", "0 : {1}", "c : [1]", "0 : null"} }), - ("compare_selections", new object[]{new string[] {"37,37"} }), // in front of @.`30,47`[0].c[0] - ("insert_text", new object[]{11, "\r\n"}), - ("compare_text", new object[]{"[1,2,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), - // TEST QUERY ONLY SOME SELECTIONS - ("tree_query", new object[]{"@..g`[bc]`"}), - ("treenode_click", new object[]{new string[]{"15,30 : [1]", "0 : 1.5"} }), - ("compare_selections", new object[]{new string[]{"26,26" } }), - ("treenode_click", new object[]{new string[] {"32,49 : [1]", "0 : [1]", "0 : null"} }), - ("compare_selections", new object[]{new string[] {"39,39"} }), - // TEST DELETIONS/INSERTIONS THAT START AND END WITHIN A SINGLE SELECTION - ("delete_text", new object[]{2, 2}), - ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), - ("tree_query", new object[]{"@..*[is_num(@)][@ >= 2]"}), - ("treenode_click", new object[]{new string[] {"0,9 : []"} }), - ("treenode_click", new object[]{new string[] {"13,28 : [1]", "0 : 3"} }), - ("compare_selections", new object[]{new string[] {"18,18"} }), - ("insert_text", new object[]{27, ", \"c\": 0"}), // add new key to second JSON - ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5, \"c\": 0}\r\n[{\"c\":[null]},-7]"}), - ("tree_query", new object[]{"@..c"}), - ("treenode_click", new object[]{new string[] {"13,36 : [1]", "0 : 0"} }), - ("treenode_click", new object[]{new string[] {"38,55 : [1]"} }), - // TEST SELECT_EVERY_VALID - ("overwrite", new object[]{ "Errör 1 [foo]: [1,2,NaN]\r\nWarning 2: {\"ä\":3}\r\nInfo 3 {bar}: [[6,{\"b\":false}],-7]\r\nError 4: \"baz\" \"quz\"\r\nError 5: \"string with no close quote\r\nError 6: not {\"even\" [json" }), - ("select", new object[]{new string[]{"0,0"} }), - ("select_every_valid", new object[]{}), - ("compare_selections", new object[]{new string[] { "16,25", "38,46", "62,82", "93,98", "99,104" } }), - ("compare_path_to_position", new object[]{22, "[`16,25`][2]"}), - ("compare_path_to_position", new object[]{101, "[`99,104`]"}), - ("compare_path_to_position", new object[]{74, "[`62,82`][0][1].b"}), - // TEST SELECT_EVERY_VALID ON A SUBSET OF THE FILE - ("select", new object[]{new string[] {"1,46", "93,100", "161,167"} }), - ("select_every_valid", new object[]{}), - ("compare_selections", new object[]{new string[] {"16,25", "38,46", "93,98", "161,167"} }), - // TEST SORT FORM - ("overwrite", new object[]{"[1,3,2]\r\n[6,5,44]\r\n[\"a\",\"c\",\"boo\"]"}), - ("select", new object[]{new string[]{"0,0"} }), - ("select_every_valid", new object[]{}), - ("compare_selections", new object[]{ new string[] { "0,7", "9,17", "19,34" } }), - ("sort_form_open", new object[]{}), - ("sort_form_run", new object[]{".g`^(?!0,)`", true, false, 1, ""}), // sort selections not starting at 0, as strings, ascending - ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"a\",\r\n \"boo\",\r\n \"c\"\r\n]"}), - ("sort_form_run", new object[]{"", true, true, 3, "s_len(str(@))"}), // sort all arrays biggest to smallest using the length of a node's string representation as key - ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), - // TEST SELECTING NON-JSON RANGE DOES NOT FORGET SELECTIONS - ("select", new object[]{new string[] {"8,15"} }), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[\"boo\",\"a\",\"c\"]"}), - // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC - ("delete_text", new object[]{20, 5}), // select treenode json and json children with array - ("insert_text", new object[]{20, "[1, NaN,\"b\"]"}), - ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[[1, NaN,\"b\"],\"a\",\"c\"]"}), - ("tree_query", new object[]{"@"}), - ("treenode_click", new object[]{new string[] {"19,41 : [3]", "0 : [3]"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"20,32"} }), - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"21,22", "24,27", "28,31"} }), - ("delete_text", new object[]{20, 12}), // now select treenode json and json children with object - ("insert_text", new object[]{20, "{\"a\": [null], \"b\": {}}"}), - ("tree_query", new object[]{"@"}), - ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"20,42"} }), - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"26,32", "39,41"} }), - ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}", "a : [1]", "0 : null"} }), // try selecting treenode json with scalar - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"27,31"} }), - ("delete_text", new object[]{20, 22}), // revert to how it was before select-treenode-json tests - ("insert_text", new object[]{20, "\"boo\""}), - ("tree_query", new object[]{"@"}), - ("treenode_click", new object[]{new string[] {"9,17 : [3]"} }), // try selecting json and json children on a different array - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"10,12", "13,14", "15,16"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"9,17"} }), - // TEST FILE WITH ONLY ONE JSON DOCUMENT - ("file_open", new object[]{1}), - ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]//a"}), - // TEST PRETTY-PRINT WITH COMMENTS AND TABS - ("pretty_print", new object[]{true, true}), - ("compare_text", new object[]{"[\r\n\t[\r\n\t\t\"Я\",\r\n\t\t1,\r\n\t\t\"a\"\r\n\t],\r\n\t// foo\r\n\t[\r\n\t\t\"◐\",\r\n\t\t2,\r\n\t\t\"b\"\r\n\t],\r\n\t// bar\r\n\t[\r\n\t\t\"ồ\",\r\n\t\t3,\r\n\t\t\"c\"\r\n\t],\r\n\t// baz\r\n\t[\r\n\t\t\"ェ\",\r\n\t\t4,\r\n\t\t\"d\"\r\n\t],\r\n\t[\r\n\t\t\"草\",\r\n\t\t5,\r\n\t\t\"e\"\r\n\t],\r\n\t[\r\n\t\t\"😀\",\r\n\t\t6,\r\n\t\t\"f\"\r\n\t]\r\n]\r\n//a\r\n"}), - // TEST PRETTY-PRINT WITH COMMENTS ONLY - ("pretty_print", new object[]{false, true}), - ("compare_text", new object[]{"[\r\n [\r\n \"Я\",\r\n 1,\r\n \"a\"\r\n ],\r\n // foo\r\n [\r\n \"◐\",\r\n 2,\r\n \"b\"\r\n ],\r\n // bar\r\n [\r\n \"ồ\",\r\n 3,\r\n \"c\"\r\n ],\r\n // baz\r\n [\r\n \"ェ\",\r\n 4,\r\n \"d\"\r\n ],\r\n [\r\n \"草\",\r\n 5,\r\n \"e\"\r\n ],\r\n [\r\n \"😀\",\r\n 6,\r\n \"f\"\r\n ]\r\n]\r\n//a\r\n" -}), - // TEST PRETTY-PRINT WITH TABS ONLY - ("pretty_print", new object[]{true}), - ("compare_text", new object[]{"[\r\n\t[\"Я\", 1, \"a\"],\r\n\t[\"◐\", 2, \"b\"],\r\n\t[\"ồ\", 3, \"c\"],\r\n\t[\"ェ\", 4, \"d\"],\r\n\t[\"草\", 5, \"e\"],\r\n\t[\"😀\", 6, \"f\"]\r\n]"}), - // TEST TREE ON WHOLE DOCUMENT - ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]"}), - ("tree_open", new object[]{}), - ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"◐\"" } }), - ("compare_selections", new object[]{new string[] {"36,36"} }), - ("compare_path_to_position", new object[]{36, "[1][0]"}), - ("treenode_click", new object[]{ new string[] { "5 : [3]", "1 : 6" } }), - ("compare_selections", new object[]{new string[] {"146,146"} }), - ("compare_path_to_position", new object[]{147, "[5][1]"}), - ("tree_query", new object[]{"@[:][1] = @ + 3" }), // mutate document - ("compare_text", new object[]{"[\r\n [\"Я\", 4, \"a\"],\r\n [\"◐\", 5, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n [\"草\", 8, \"e\"],\r\n [\"😀\", 9, \"f\"]\r\n]"}), - ("compare_path_to_position", new object[]{126, "[5][1]"}), - // TEST SELECT NON-JSON RANGE AND MAKE SURE WHOLE DOCUMENT STILL PARSED (WHEN NOT IN SELECTION MODE) - ("select", new object[]{new string[] {"1,7"} }), - ("compress", new object[]{}), - ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],[\"草\",8,\"e\"],[\"😀\",9,\"f\"]]"}), - // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC - ("tree_query", new object[]{"@"}), - ("treenode_click", new object[]{new string[] {"2 : [3]"} }), // try selecting json and json children with array - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"29,34", "35,36", "37,40"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"28,41"} }), - ("treenode_click", new object[]{new string[] {"3 : [3]", "0 : \"ェ\"" } }), // try selecting treenode json with scalar - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"43,48"} }), - ("overwrite", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\"😀\",9,\"f\"]]"}), // try selecting treenode json and json children with object - ("tree_query", new object[]{"@"}), - ("treenode_click", new object[]{new string[] {"4 : {3}"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"56,92"} }), - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"63,66", "71,74", "85,91"} }), - ("select", new object[]{new string[]{"0,0"} }), - ("tree_query", new object[]{"@![4][@[1] >= 7]"}), // try selecting treenode json children when parent is RemesPath query node - ("treenode_click", new object[]{new string[]{ } }), - ("select_treenode_json_children", new object[]{}), - ("pretty_print", new object[]{}), - ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\r\n \"ェ\",\r\n 7,\r\n \"d\"\r\n],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\r\n \"😀\",\r\n 9,\r\n \"f\"\r\n]]"}), - // TEST NON-MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS - ("select_whole_doc", new object[]{}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), - ("tree_query", new object[]{"var a = @[0];\r\nvar b = @[1];\r\nvar c = @[-1];\r\nvar d = a + b * s_len(str(c));"}), - ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), - ("treenode_click", new object[]{new string[] { "0 : \"Я◐◐\"" } }), - ("treenode_click", new object[]{new string[] {"1 : 9"} }), - ("treenode_click", new object[]{new string[] { "2 : \"ab\"" } }), - ("tree_query", new object[]{"var mod_3s = (@[:][type(@) != object])[:]->append(@, @[1] % 3);\r\n" + - "var gb_m3 = group_by(mod_3s, -1);\r\n" + - "gb_m3.`1`"}), - ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), - ("treenode_click", new object[]{new string[] { "0 : [4]", "2 : \"a\"" } }), - ("treenode_click", new object[]{new string[] { "1 : [4]", "1 : 7" } }), - // TEST MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS - ("tree_query", new object[]{"var a = @[0];\r\n" + - "var b = @[1];\r\n" + - "var c = @[-1];\r\n" + - "var d = a + b * s_len(str(c));\r\n" + - "a[0] = @ + d[0];\r\nb[1] = @ + c[1];\r\nc[2] = @ + a[0]"}), - ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 14, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}), - ("treenode_click", new object[]{new string[]{"0 : [3]", "0 : \"ЯЯ◐◐\"" } }), - ("compare_selections", new object[]{new string[]{ "8,8" } }), - ("treenode_click", new object[]{new string[] {"1 : [3]", "1 : 14" } }), - ("compare_selections", new object[]{new string[]{ "44,44" } }), - ("treenode_click", new object[]{new string[]{"5 : [3]", "2 : \"fЯЯ◐◐\"" } }), - ("compare_selections", new object[]{new string[]{ "160,160" } }), - ("tree_query", new object[]{"var bumba = @[1][1]; bumba = @ / 7; bumba"}), - ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 2.0, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}), - ("treenode_click", new object[]{ new string[] { } }), - ("compare_selections", new object[]{new string[] {"44,44"} }), - ("tree_compare_query_result", new object[]{"2.0"}), - // TEST THAT JAVASCRIPT AND PYTHON COMMENTS AT EOF DON'T CAUSE PROBLEMS - ("overwrite", new object[]{"\r\n[1,2,\r\n3]#"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,3]"}), - ("overwrite", new object[]{"[1,2,3]#"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,3]"}), - ("overwrite", new object[]{"[1\r\n ,2,\r\n3]//"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,3]"}), - ("overwrite", new object[]{"[1,2,\r\n3]#bar"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,3]"}), - ("overwrite", new object[]{"[1,2,-9e15]\r\n//foo"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,-9E+15]"}), - // TEST PARSE JSON LINES - ("overwrite", new object[]{"[1,2,3]\r\n{\"a\": 1, \"b\": [-3,-4]}\r\n-7\r\nfalse"}), - ("tree_open", new object[]{}), // to close the tree so it can be reopened - ("tree_open", new object[]{DocumentType.JSONL}), - ("tree_query", new object[]{"@..* [abs(+@) < 3] = @ / 4"}), // divide values in open range (-3, 3) by 4; this should keep the file in JSON Lines format - ("compare_text", new object[]{"[0.25,0.5,3]\n{\"a\":0.25,\"b\":[-3,-4]}\n-7\n0.0"}), - ("tree_query", new object[]{"@"}), // re-parse document - ("treenode_click", new object[]{new string[] {"3 : 0.0"} }), - ("compare_selections", new object[]{new string[] {"39,39"} }), - ("compare_path_to_position", new object[]{32, "[1].b[1]"}), - // TEST PARSE INI FILE - ("overwrite", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\n bar=2\r\n [дaz]\r\n baz=3\r\n ;b\r\n baz2 = 7 \r\n[quz]\r\nquz=4\r\n;c"}), - ("tree_open", new object[]{}), - ("tree_open", new object[]{DocumentType.INI}), - ("tree_query", new object[]{"@..g`z`"}), - ("treenode_click", new object[]{new string[] {"0 : {2}", "baz2 : \"7 \""} }), - ("compare_selections", new object[]{new string[]{"63,63" } }), - ("compare_path_to_position", new object[]{81, ".quz.quz"}), - ("tree_query", new object[]{"@.bar.bar = @ * int(@)"}), // edit one value - ("compare_text", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\nbar=22\r\n[дaz]\r\nbaz=3\r\n;b\r\nbaz2=7 \r\n[quz]\r\nquz=4\r\n;c\r\n"}), - // TEST RUNNING SAME QUERY MULTIPLE TIMES ON SAME INPUT DOES NOT HAVE DIFFERENT RESULTS - // test when mutating a compile-time constant array - ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}), - ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}), - ("treenode_click", new object[]{new string[] {"1 : 2"} }), - // test when mutating a compile-time constant loop variable - ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}), - ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}), - ("treenode_click", new object[]{new string[] {"0 : 2"} }), - ("treenode_click", new object[]{new string[] {"1 : 2"} }), - // test when mutating projections (both object and array) - ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}), - ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}), - ("treenode_click", new object[]{new string[] {"0 : [2]", "1 : 2"} }), - ("treenode_click", new object[]{new string[] {"1 : {1}", "a : 2"} }), - // test when mutating map projection - ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}), - ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}), - ("treenode_click", new object[]{new string[] {"0 : 0"} }), - // TEST ASSIGNMENT OPERATIONS ON MULTIPLE SELECTIONS (back to file with three arrays that were sorted by the sort form) - ("file_open", new object[]{0}), - ("tree_query", new object[]{"@[:][is_num(@)] = @ / 4"}), - ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n]\r\n[\r\n 11.0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), - ("treenode_click", new object[]{new string[] {"37,72 : [3]", "1 : 1.25"} }), - ("compare_selections", new object[]{ new string[] { "55,55" } }), - // TEST DELETION THAT STARTS BEFORE A SELECTION AND ENDS INSIDE IT - ("insert_text", new object[]{47, "["}), - ("delete_text", new object[]{ 35, 12 }), - ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n][0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[0.25,0.75,0.5][0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), - // TEST INSERTION THAT BEGINS ON THE FIRST CHAR OF A SELECTION - ("insert_text", new object[]{15, "blah"}), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), - // TEST DELETION THAT STARTS INSIDE A SELECTION AND ENDS AFTER IT - ("insert_text", new object[]{26, "]"}), - ("delete_text", new object[]{27, 7}), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), - // TEST QUERY THAT PRODUCES OBJECT WITH NON-"START,END" KEYS ON A FILE WITH SELECTIONS - ("tree_query", new object[]{"j`{\"a\": \"foo\", \"b\\n\": [1, 2]}`"}), - ("treenode_click", new object[]{new string[] {"a : \"foo\""} }), - ("treenode_click", new object[]{new string[] {"b\\n : [2]", "1 : 2"} }), - // TEST MULTI-STATEMENT QUERY THAT DOESN'T MUTATE ON A FILE WITH SELECTIONS - ("tree_query", new object[]{"var s = str(@);\r\n" + - "var sl = s_len(s);\r\n" + - "(s + ` `) * sl"}), - ("treenode_click", new object[]{new string[] {"27,42 : [3]", "0 : \"boo boo boo \""} }), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), - ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" + - "var s_lens = s_len(this_s[:][1]);\r\n" + - "var min_slen = min(s_lens);\r\n" + - "var shortest_strs = this_s[s_lens == min_slen][0]"}), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), - ("treenode_click", new object[]{ new string[] { "0,15 : [1]", "0 : 0.5"} }), - ("compare_selections", new object[]{new string[]{"11,11" } }), - ("treenode_click", new object[]{ new string[] { "27,42 : [2]", "1 : \"c\""} }), - ("compare_selections", new object[]{new string[]{"38,38" } }), - // TEST MULTI-STATEMENT QUERY THAT *DOES* MUTATE ON A FILE WITH SELECTIONS - ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" + - "var s_lens = s_len(this_s[:][1]);\r\n" + - "var max_slen = max(s_lens);\r\n" + - "var longest_strs = this_s[s_lens == max_slen][0];\r\n" + - "longest_strs = str(@) + ` is champion!`;\r\n" + - "longest_strs"}), - ("compare_text", new object[]{"[\r\n \"0.25 is champion!\",\r\n \"0.75 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!\"\r\n][\r\n \"boo is champion!\",\r\n \"a\",\r\n \"c\"\r\n]"}), - ("treenode_click", new object[]{ new string[] { "69,106 : [1]", "0 : \"1.25 is champion!\""} }), - ("treenode_click", new object[]{ new string[] { "106,154 : [1]", "0 : \"boo is champion!\""} }), - ("tree_compare_query_result", new object[]{"{\"0,65\": [\"0.25 is champion!\", \"0.75 is champion!\"], \"106,154\": [\"boo is champion!\"], \"69,106\": [\"1.25 is champion!\"]}"}), - // TEST PRINTING SELECTION-BASED FILE WITH TABS - ("pretty_print", new object[]{true}), - ("compare_text", new object[]{"[\r\n\t\"0.25 is champion!\",\r\n\t\"0.75 is champion!\",\r\n\t0.5\r\n]blah[\r\n\t0,\r\n\t\"1.25 is champion!\"\r\n][\r\n\t\"boo is champion!\",\r\n\t\"a\",\r\n\t\"c\"\r\n]"}), - // TEST QUERIES WITH LOOP VARIABLES ON SELECTION-BASED FILE - ("tree_query", new object[]{// find all the strings in the array, then add the i^th string in the array to the i^th-from-last string in the array - "var strs = @[:][is_str(@)];\r\n" + - "var strs_cpy = str(strs);\r\n" + - // need to iterate through (the i^th string, a *copy* of the i^th string, and a *copy* of the i^th-from-last string), - // because if we don't we will mutate some strings multiple times due to the in-place nature of mutation - "for s = zip(strs, strs_cpy, strs_cpy[::-1]);\r\n" + - " s[0] = s[1] + s[2];\r\n" + - "end for" - }), - ("compare_text", new object[]{"[\r\n \"0.25 is champion!0.75 is champion!\",\r\n \"0.75 is champion!0.25 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!1.25 is champion!\"\r\n][\r\n \"boo is champion!c\",\r\n \"aa\",\r\n \"cboo is champion!\"\r\n]"}), - // TEST REGEX SEARCH FORM (REGEX MODE) - ("file_open", new object[]{2, "txt"}), - ("overwrite", new object[]{"\u200a\u202f\n foo: 1\nBA\u042f: \u00a0-3\nbaz: +85"}), - ("regex_search", new object[]{ - "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(INT)\",\"ignoreCase\":true,\"fullMatch\":false,\"numCols\":[1]}" - }), - ("treenode_click", new object[]{new string[] {"0 : [2]"} }), - ("compare_selections", new object[]{new string[] {"7,7"} }), - ("treenode_click", new object[]{new string[] {"1 : [2]", "1 : -3"} }), - ("compare_selections", new object[]{new string[] {"23,23"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"23,25"} }), - ("treenode_click", new object[]{new string[] {"2 : [2]", "1 : 85"} }), - ("compare_selections", new object[]{new string[] {"31,31"} }), - ("regex_search", new object[]{ - "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(NUMBER)\",\"ignoreCase\":false,\"fullMatch\":true}" - }), - ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"baz: +85\""} }), - ("treenode_click", new object[]{new string[] {"0 : [3]", "1 : \"foo\""} }), - ("treenode_click", new object[]{new string[] {"0 : [3]", "2 : \"1\""} }), - ("tree_query", new object[]{ - "@ = s_sub(@,\r\n" + - " g`(?i)^\\x20*([a-zЯ]+):\\s+(INT)`,\r\n" + - " ifelse(float(@[2]) < 0,\r\n" + - " str(loop()) + @[0],\r\n" + - " str(loop()) + `. ` + @[1] + ` (` + str(int(@[2])) + `)`\r\n" + - " )\r\n" + - ")"}), - ("compare_text", new object[]{"\u200a\u202f\n1. foo (1)\n2BA\u042f: \u00a0-3\n3. baz (85)"}), - // TEST REGEX SEARCH FORM (CSV MODE) - ("overwrite", new object[]{ - "ab\tc\t😀\tf\r\n" + - "'fo\nö'\t7\tbar\t-8.5\r\n" + - "baz\t19\t''\t-4e3\r\n" + - "'zorq'\t\tkywq\t'.75'" - }), - ("regex_search", new object[] - { - "{\"csv\":true,\"delim\":\"\\\\t\",\"quote\":\"'\",\"newline\":\"\\r\\n\",\"header\":\"d\",\"nColumns\":4,\"numCols\":[-1,1]}" - }), - ("treenode_click", new object[]{new string[]{"0 : {4}", "ab : \"fo\\nö\"" } }), - ("treenode_click", new object[]{new string[]{"1 : {4}", "c : 19"} }), - ("treenode_click", new object[]{new string[]{"1 : {4}", "😀 : \"\"" } }), - ("treenode_click", new object[]{new string[]{"2 : {4}", "f : \".75\""} }), - ("compare_selections", new object[]{new string[] {"62,62"} }), - ("regex_search", new object[] + List<(string command, object[] args)> testcases = new List<(string command, object[] args)> { - "{\"csv\": true, \"delim\": \"\\\\t\", \"quote\": \"'\", \"newline\": \"\\r\\n\", \"header\": 1, \"nColumns\": 4, \"numCols\": [1]}" - }), - ("treenode_click", new object[]{new string[] {"0 : [4]", "3 : \"f\""} }), - ("treenode_click", new object[]{new string[] {"1 : [4]", "1 : 7"} }), - ("treenode_click", new object[]{new string[] {"2 : [4]", "3 : \"-4e3\""} }), - ("treenode_click", new object[]{new string[] {"3 : [4]", "0 : \"zorq\""} }), - // TEST SELECT ALL CHILDREN IN CSV FILE - ("tree_query", new object[]{"s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h)[:][0]"}), - ("treenode_click", new object[]{new string[]{ } }), // click root - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"0,2", "13,20", "33,36", "49,55"} }), - ("treenode_click", new object[]{new string[]{ "1 : \"fo\\nö\"" } }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"13,20"} }), - // MUTATE CSV FILE WITH QUERY - ("tree_query", new object[]{ - "var c = s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h, 1);\r\n" + - "c[:][1][is_num(@)] = @/4;\r\n" + - "@ = to_csv(c,`,`, `\\n`, `\"`)"}), - ("compare_text", new object[]{"ab,c,😀,f\n\"fo\nö\",1.75,bar,-8.5\nbaz,4.75,,-4e3\nzorq,,kywq,.75\n"}), - // TEST REGEX SEARCH FORM (SINGLE-CAPTURE-GROUP CSV AND REGEX MODES) - ("overwrite", new object[]{" 草1\nェ2\n◐3\n'4"}), // last row has CSV quote char to make sure CSV delimiter is forgotten now that a non-csv search was made - ("regex_search", new object[]{ - "{\"csv\":false,\"regex\":\"^\\\\S\\\\d\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}" - }), - ("treenode_click", new object[]{new string[]{ } }), // click root - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"6,10", "11,15", "16,18"} }), - ("treenode_click", new object[]{new string[] { "1 : \"◐3\"" } }), - ("compare_selections", new object[]{new string[] {"11,11"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"11,15"} }), - ("regex_search", new object[]{ - "{\"csv\":false,\"regex\":\"^\\\\S(\\\\d)\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}" // this time capture just the number after the nonspace - }), - ("treenode_click", new object[]{new string[] { "1 : \"3\"" } }), - ("compare_selections", new object[]{new string[] {"14,14"} }), - ("regex_search", new object[] - { - "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": \"\\n\", \"header\": 0, \"nColumns\": 1}" - }), - ("treenode_click", new object[]{new string[] { "0 : \"ェ2\"" } }), - ("compare_selections", new object[]{new string[] {"6,6"} }), - ("regex_search", new object[] - { - "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": 1, \"header\": 2, \"nColumns\": 1}" - }), - ("treenode_click", new object[]{new string[] { "0 : {1}", " 草1 : \"ェ2\"" } }), - ("compare_selections", new object[]{new string[] {"6,6"} }), - }; + ("overwrite", new object[]{"[1, 2, false]\r\n{\"a\": 3, \"b\": 1.5}\r\n[{\"c\": [null]}, -7]"}), // first line: 0 to 13, second line: 15 to 33, third line: 35 to 54 + ("select", new object[]{new string[]{ "0,13", "15,33", "35,54" } }), + // when pretty-printed in PPrint style looks like this: + //[ + // 1, + // 2, + // false + //] + //{ + // "a": 3, + // "b": 1.5 + //} + //[ + // {"c": [null]}, + // -7 + //] + // first JSON: 0 to 31, second JSON: 33 to 64, third JSON: 66 to 98 + ("pretty_print", new object[]{ }), + ("compare_text", new object[]{ "[\r\n 1,\r\n 2,\r\n false\r\n]\r\n{\r\n \"a\": 3,\r\n \"b\": 1.5\r\n}\r\n[\r\n {\"c\": [null]},\r\n -7\r\n]"}), + ("compare_selections", new object[]{new string[]{"0,31", "33,64", "66,98"} }), + // when compressed looks like this: + //[1,2,false] + //{"a":3,"b":1.5} + //[{"c":[null]},-7] + // first JSON: 0 to 11, second JSON: 13 to 28, third JSON: 30 to 47 + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,false]\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), + ("compare_selections", new object[]{new string[] {"0,11", "13,28", "30,47"} }), + // TEST THAT TREENODE CLICKS GO TO RIGHT LOCATION + ("tree_open", new object[]{}), + ("treenode_click", new object[]{ new string[] { "0,11 : [3]", "2 : false"} }), + ("compare_selections", new object[]{new string[] {"5,5"} }), // in front of @.`0,11`[2] + ("treenode_click", new object[]{ new string[] {"30,47 : [2]", "0 : {1}", "c : [1]", "0 : null"} }), + ("compare_selections", new object[]{new string[] {"37,37"} }), // in front of @.`30,47`[0].c[0] + ("insert_text", new object[]{11, "\r\n"}), + ("compare_text", new object[]{"[1,2,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), + // TEST QUERY ONLY SOME SELECTIONS + ("tree_query", new object[]{"@..g`[bc]`"}), + ("treenode_click", new object[]{new string[]{"15,30 : [1]", "0 : 1.5"} }), + ("compare_selections", new object[]{new string[]{"26,26" } }), + ("treenode_click", new object[]{new string[] {"32,49 : [1]", "0 : [1]", "0 : null"} }), + ("compare_selections", new object[]{new string[] {"39,39"} }), + // TEST DELETIONS/INSERTIONS THAT START AND END WITHIN A SINGLE SELECTION + ("delete_text", new object[]{2, 2}), + ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), + ("tree_query", new object[]{"@..*[is_num(@)][@ >= 2]"}), + ("treenode_click", new object[]{new string[] {"0,9 : []"} }), + ("treenode_click", new object[]{new string[] {"13,28 : [1]", "0 : 3"} }), + ("compare_selections", new object[]{new string[] {"18,18"} }), + ("insert_text", new object[]{27, ", \"c\": 0"}), // add new key to second JSON + ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5, \"c\": 0}\r\n[{\"c\":[null]},-7]"}), + ("tree_query", new object[]{"@..c"}), + ("treenode_click", new object[]{new string[] {"13,36 : [1]", "0 : 0"} }), + ("treenode_click", new object[]{new string[] {"38,55 : [1]"} }), + // TEST SELECT_EVERY_VALID + ("overwrite", new object[]{ "Errör 1 [foo]: [1,2,NaN]\r\nWarning 2: {\"ä\":3}\r\nInfo 3 {bar}: [[6,{\"b\":false}],-7]\r\nError 4: \"baz\" \"quz\"\r\nError 5: \"string with no close quote\r\nError 6: not {\"even\" [json" }), + ("select", new object[]{new string[]{"0,0"} }), + ("select_every_valid", new object[]{}), + ("compare_selections", new object[]{new string[] { "16,25", "38,46", "62,82", "93,98", "99,104" } }), + ("compare_path_to_position", new object[]{22, "[`16,25`][2]"}), + ("compare_path_to_position", new object[]{101, "[`99,104`]"}), + ("compare_path_to_position", new object[]{74, "[`62,82`][0][1].b"}), + // TEST SELECT_EVERY_VALID ON A SUBSET OF THE FILE + ("select", new object[]{new string[] {"1,46", "93,100", "161,167"} }), + ("select_every_valid", new object[]{}), + ("compare_selections", new object[]{new string[] {"16,25", "38,46", "93,98", "161,167"} }), + // TEST SORT FORM + ("overwrite", new object[]{"[1,3,2]\r\n[6,5,44]\r\n[\"a\",\"c\",\"boo\"]"}), + ("select", new object[]{new string[]{"0,0"} }), + ("select_every_valid", new object[]{}), + ("compare_selections", new object[]{ new string[] { "0,7", "9,17", "19,34" } }), + ("sort_form_open", new object[]{}), + ("sort_form_run", new object[]{".g`^(?!0,)`", true, false, 1, ""}), // sort selections not starting at 0, as strings, ascending + ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"a\",\r\n \"boo\",\r\n \"c\"\r\n]"}), + ("sort_form_run", new object[]{"", true, true, 3, "s_len(str(@))"}), // sort all arrays biggest to smallest using the length of a node's string representation as key + ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), + // TEST SELECTING NON-JSON RANGE DOES NOT FORGET SELECTIONS + ("select", new object[]{new string[] {"8,15"} }), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[\"boo\",\"a\",\"c\"]"}), + // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC + ("delete_text", new object[]{20, 5}), // select treenode json and json children with array + ("insert_text", new object[]{20, "[1, NaN,\"b\"]"}), + ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[[1, NaN,\"b\"],\"a\",\"c\"]"}), + ("tree_query", new object[]{"@"}), + ("treenode_click", new object[]{new string[] {"19,41 : [3]", "0 : [3]"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"20,32"} }), + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"21,22", "24,27", "28,31"} }), + ("delete_text", new object[]{20, 12}), // now select treenode json and json children with object + ("insert_text", new object[]{20, "{\"a\": [null], \"b\": {}}"}), + ("tree_query", new object[]{"@"}), + ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"20,42"} }), + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"26,32", "39,41"} }), + ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}", "a : [1]", "0 : null"} }), // try selecting treenode json with scalar + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"27,31"} }), + ("delete_text", new object[]{20, 22}), // revert to how it was before select-treenode-json tests + ("insert_text", new object[]{20, "\"boo\""}), + ("tree_query", new object[]{"@"}), + ("treenode_click", new object[]{new string[] {"9,17 : [3]"} }), // try selecting json and json children on a different array + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"10,12", "13,14", "15,16"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"9,17"} }), + // TEST FILE WITH ONLY ONE JSON DOCUMENT + ("file_open", new object[]{1}), + ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]//a"}), + // TEST PRETTY-PRINT WITH COMMENTS AND TABS + ("pretty_print", new object[]{true, true}), + ("compare_text", new object[]{"[\r\n\t[\r\n\t\t\"Я\",\r\n\t\t1,\r\n\t\t\"a\"\r\n\t],\r\n\t// foo\r\n\t[\r\n\t\t\"◐\",\r\n\t\t2,\r\n\t\t\"b\"\r\n\t],\r\n\t// bar\r\n\t[\r\n\t\t\"ồ\",\r\n\t\t3,\r\n\t\t\"c\"\r\n\t],\r\n\t// baz\r\n\t[\r\n\t\t\"ェ\",\r\n\t\t4,\r\n\t\t\"d\"\r\n\t],\r\n\t[\r\n\t\t\"草\",\r\n\t\t5,\r\n\t\t\"e\"\r\n\t],\r\n\t[\r\n\t\t\"😀\",\r\n\t\t6,\r\n\t\t\"f\"\r\n\t]\r\n]\r\n//a\r\n"}), + // TEST PRETTY-PRINT WITH COMMENTS ONLY + ("pretty_print", new object[]{false, true}), + ("compare_text", new object[]{"[\r\n [\r\n \"Я\",\r\n 1,\r\n \"a\"\r\n ],\r\n // foo\r\n [\r\n \"◐\",\r\n 2,\r\n \"b\"\r\n ],\r\n // bar\r\n [\r\n \"ồ\",\r\n 3,\r\n \"c\"\r\n ],\r\n // baz\r\n [\r\n \"ェ\",\r\n 4,\r\n \"d\"\r\n ],\r\n [\r\n \"草\",\r\n 5,\r\n \"e\"\r\n ],\r\n [\r\n \"😀\",\r\n 6,\r\n \"f\"\r\n ]\r\n]\r\n//a\r\n" + }), + // TEST PRETTY-PRINT WITH TABS ONLY + ("pretty_print", new object[]{true}), + ("compare_text", new object[]{"[\r\n\t[\"Я\", 1, \"a\"],\r\n\t[\"◐\", 2, \"b\"],\r\n\t[\"ồ\", 3, \"c\"],\r\n\t[\"ェ\", 4, \"d\"],\r\n\t[\"草\", 5, \"e\"],\r\n\t[\"😀\", 6, \"f\"]\r\n]"}), + // TEST TREE ON WHOLE DOCUMENT + ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]"}), + ("tree_open", new object[]{}), + ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"◐\"" } }), + ("compare_selections", new object[]{new string[] {"36,36"} }), + ("compare_path_to_position", new object[]{36, "[1][0]"}), + ("treenode_click", new object[]{ new string[] { "5 : [3]", "1 : 6" } }), + ("compare_selections", new object[]{new string[] {"146,146"} }), + ("compare_path_to_position", new object[]{147, "[5][1]"}), + ("tree_query", new object[]{"@[:][1] = @ + 3" }), // mutate document + ("compare_text", new object[]{"[\r\n [\"Я\", 4, \"a\"],\r\n [\"◐\", 5, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n [\"草\", 8, \"e\"],\r\n [\"😀\", 9, \"f\"]\r\n]"}), + ("compare_path_to_position", new object[]{126, "[5][1]"}), + // TEST SELECT NON-JSON RANGE AND MAKE SURE WHOLE DOCUMENT STILL PARSED (WHEN NOT IN SELECTION MODE) + ("select", new object[]{new string[] {"1,7"} }), + ("compress", new object[]{}), + ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],[\"草\",8,\"e\"],[\"😀\",9,\"f\"]]"}), + // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC + ("tree_query", new object[]{"@"}), + ("treenode_click", new object[]{new string[] {"2 : [3]"} }), // try selecting json and json children with array + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"29,34", "35,36", "37,40"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"28,41"} }), + ("treenode_click", new object[]{new string[] {"3 : [3]", "0 : \"ェ\"" } }), // try selecting treenode json with scalar + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"43,48"} }), + ("overwrite", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\"😀\",9,\"f\"]]"}), // try selecting treenode json and json children with object + ("tree_query", new object[]{"@"}), + ("treenode_click", new object[]{new string[] {"4 : {3}"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"56,92"} }), + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"63,66", "71,74", "85,91"} }), + ("select", new object[]{new string[]{"0,0"} }), + ("tree_query", new object[]{"@![4][@[1] >= 7]"}), // try selecting treenode json children when parent is RemesPath query node + ("treenode_click", new object[]{new string[]{ } }), + ("select_treenode_json_children", new object[]{}), + ("pretty_print", new object[]{}), + ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\r\n \"ェ\",\r\n 7,\r\n \"d\"\r\n],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\r\n \"😀\",\r\n 9,\r\n \"f\"\r\n]]"}), + // TEST NON-MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS + ("select_whole_doc", new object[]{}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), + ("tree_query", new object[]{"var a = @[0];\r\nvar b = @[1];\r\nvar c = @[-1];\r\nvar d = a + b * s_len(str(c));"}), + ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), + ("treenode_click", new object[]{new string[] { "0 : \"Я◐◐\"" } }), + ("treenode_click", new object[]{new string[] {"1 : 9"} }), + ("treenode_click", new object[]{new string[] { "2 : \"ab\"" } }), + ("tree_query", new object[]{"var mod_3s = (@[:][type(@) != object])[:]->append(@, @[1] % 3);\r\n" + + "var gb_m3 = group_by(mod_3s, -1);\r\n" + + "gb_m3.`1`"}), + ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), + ("treenode_click", new object[]{new string[] { "0 : [4]", "2 : \"a\"" } }), + ("treenode_click", new object[]{new string[] { "1 : [4]", "1 : 7" } }), + // TEST MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS + ("tree_query", new object[]{"var a = @[0];\r\n" + + "var b = @[1];\r\n" + + "var c = @[-1];\r\n" + + "var d = a + b * s_len(str(c));\r\n" + + "a[0] = @ + d[0];\r\nb[1] = @ + c[1];\r\nc[2] = @ + a[0]"}), + ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 14, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}), + ("treenode_click", new object[]{new string[]{"0 : [3]", "0 : \"ЯЯ◐◐\"" } }), + ("compare_selections", new object[]{new string[]{ "8,8" } }), + ("treenode_click", new object[]{new string[] {"1 : [3]", "1 : 14" } }), + ("compare_selections", new object[]{new string[]{ "44,44" } }), + ("treenode_click", new object[]{new string[]{"5 : [3]", "2 : \"fЯЯ◐◐\"" } }), + ("compare_selections", new object[]{new string[]{ "160,160" } }), + ("tree_query", new object[]{"var bumba = @[1][1]; bumba = @ / 7; bumba"}), + ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 2.0, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}), + ("treenode_click", new object[]{ new string[] { } }), + ("compare_selections", new object[]{new string[] {"44,44"} }), + ("tree_compare_query_result", new object[]{"2.0"}), + // TEST THAT JAVASCRIPT AND PYTHON COMMENTS AT EOF DON'T CAUSE PROBLEMS + ("overwrite", new object[]{"\r\n[1,2,\r\n3]#"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,3]"}), + ("overwrite", new object[]{"[1,2,3]#"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,3]"}), + ("overwrite", new object[]{"[1\r\n ,2,\r\n3]//"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,3]"}), + ("overwrite", new object[]{"[1,2,\r\n3]#bar"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,3]"}), + ("overwrite", new object[]{"[1,2,-9e15]\r\n//foo"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,-9E+15]"}), + // TEST PARSE JSON LINES + ("overwrite", new object[]{"[1,2,3]\r\n{\"a\": 1, \"b\": [-3,-4]}\r\n-7\r\nfalse"}), + ("tree_open", new object[]{}), // to close the tree so it can be reopened + ("tree_open", new object[]{DocumentType.JSONL}), + ("tree_query", new object[]{"@..* [abs(+@) < 3] = @ / 4"}), // divide values in open range (-3, 3) by 4; this should keep the file in JSON Lines format + ("compare_text", new object[]{"[0.25,0.5,3]\n{\"a\":0.25,\"b\":[-3,-4]}\n-7\n0.0"}), + ("tree_query", new object[]{"@"}), // re-parse document + ("treenode_click", new object[]{new string[] {"3 : 0.0"} }), + ("compare_selections", new object[]{new string[] {"39,39"} }), + ("compare_path_to_position", new object[]{32, "[1].b[1]"}), + // TEST PARSE INI FILE + ("overwrite", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\n bar=2\r\n [дaz]\r\n baz=3\r\n ;b\r\n baz2 = 7 \r\n[quz]\r\nquz=4\r\n;c"}), + ("set_document_type", new object[]{"INI"}), + ("tree_query", new object[]{"@..g`z`"}), + ("treenode_click", new object[]{new string[] {"0 : {2}", "baz2 : \"7 \""} }), + ("compare_selections", new object[]{new string[]{"63,63" } }), + ("compare_path_to_position", new object[]{81, ".quz.quz"}), + ("tree_query", new object[]{"@.bar.bar = @ * int(@)"}), // edit one value + ("compare_text", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\nbar=22\r\n[дaz]\r\nbaz=3\r\n;b\r\nbaz2=7 \r\n[quz]\r\nquz=4\r\n;c\r\n"}), + // TEST RUNNING SAME QUERY MULTIPLE TIMES ON SAME INPUT DOES NOT HAVE DIFFERENT RESULTS + // test when mutating a compile-time constant array + ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}), + ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}), + ("treenode_click", new object[]{new string[] {"1 : 2"} }), + // test when mutating a compile-time constant loop variable + ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}), + ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}), + ("treenode_click", new object[]{new string[] {"0 : 2"} }), + ("treenode_click", new object[]{new string[] {"1 : 2"} }), + // test when mutating projections (both object and array) + ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}), + ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}), + ("treenode_click", new object[]{new string[] {"0 : [2]", "1 : 2"} }), + ("treenode_click", new object[]{new string[] {"1 : {1}", "a : 2"} }), + // test when mutating map projection + ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}), + ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}), + ("treenode_click", new object[]{new string[] {"0 : 0"} }), + // TEST ASSIGNMENT OPERATIONS ON MULTIPLE SELECTIONS (back to file with three arrays that were sorted by the sort form) + ("file_open", new object[]{0}), + ("tree_query", new object[]{"@[:][is_num(@)] = @ / 4"}), + ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n]\r\n[\r\n 11.0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), + ("treenode_click", new object[]{new string[] {"37,72 : [3]", "1 : 1.25"} }), + ("compare_selections", new object[]{ new string[] { "55,55" } }), + // TEST DELETION THAT STARTS BEFORE A SELECTION AND ENDS INSIDE IT + ("insert_text", new object[]{47, "["}), + ("delete_text", new object[]{ 35, 12 }), + ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n][0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[0.25,0.75,0.5][0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), + // TEST INSERTION THAT BEGINS ON THE FIRST CHAR OF A SELECTION + ("insert_text", new object[]{15, "blah"}), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), + // TEST DELETION THAT STARTS INSIDE A SELECTION AND ENDS AFTER IT + ("insert_text", new object[]{26, "]"}), + ("delete_text", new object[]{27, 7}), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), + // TEST QUERY THAT PRODUCES OBJECT WITH NON-"START,END" KEYS ON A FILE WITH SELECTIONS + ("tree_query", new object[]{"j`{\"a\": \"foo\", \"b\\n\": [1, 2]}`"}), + ("treenode_click", new object[]{new string[] {"a : \"foo\""} }), + ("treenode_click", new object[]{new string[] {"b\\n : [2]", "1 : 2"} }), + // TEST MULTI-STATEMENT QUERY THAT DOESN'T MUTATE ON A FILE WITH SELECTIONS + ("tree_query", new object[]{"var s = str(@);\r\n" + + "var sl = s_len(s);\r\n" + + "(s + ` `) * sl"}), + ("treenode_click", new object[]{new string[] {"27,42 : [3]", "0 : \"boo boo boo \""} }), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), + ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" + + "var s_lens = s_len(this_s[:][1]);\r\n" + + "var min_slen = min(s_lens);\r\n" + + "var shortest_strs = this_s[s_lens == min_slen][0]"}), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), + ("treenode_click", new object[]{ new string[] { "0,15 : [1]", "0 : 0.5"} }), + ("compare_selections", new object[]{new string[]{"11,11" } }), + ("treenode_click", new object[]{ new string[] { "27,42 : [2]", "1 : \"c\""} }), + ("compare_selections", new object[]{new string[]{"38,38" } }), + // TEST MULTI-STATEMENT QUERY THAT *DOES* MUTATE ON A FILE WITH SELECTIONS + ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" + + "var s_lens = s_len(this_s[:][1]);\r\n" + + "var max_slen = max(s_lens);\r\n" + + "var longest_strs = this_s[s_lens == max_slen][0];\r\n" + + "longest_strs = str(@) + ` is champion!`;\r\n" + + "longest_strs"}), + ("compare_text", new object[]{"[\r\n \"0.25 is champion!\",\r\n \"0.75 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!\"\r\n][\r\n \"boo is champion!\",\r\n \"a\",\r\n \"c\"\r\n]"}), + ("treenode_click", new object[]{ new string[] { "69,106 : [1]", "0 : \"1.25 is champion!\""} }), + ("treenode_click", new object[]{ new string[] { "106,154 : [1]", "0 : \"boo is champion!\""} }), + ("tree_compare_query_result", new object[]{"{\"0,65\": [\"0.25 is champion!\", \"0.75 is champion!\"], \"106,154\": [\"boo is champion!\"], \"69,106\": [\"1.25 is champion!\"]}"}), + // TEST PRINTING SELECTION-BASED FILE WITH TABS + ("pretty_print", new object[]{true}), + ("compare_text", new object[]{"[\r\n\t\"0.25 is champion!\",\r\n\t\"0.75 is champion!\",\r\n\t0.5\r\n]blah[\r\n\t0,\r\n\t\"1.25 is champion!\"\r\n][\r\n\t\"boo is champion!\",\r\n\t\"a\",\r\n\t\"c\"\r\n]"}), + // TEST QUERIES WITH LOOP VARIABLES ON SELECTION-BASED FILE + ("tree_query", new object[]{// find all the strings in the array, then add the i^th string in the array to the i^th-from-last string in the array + "var strs = @[:][is_str(@)];\r\n" + + "var strs_cpy = str(strs);\r\n" + + // need to iterate through (the i^th string, a *copy* of the i^th string, and a *copy* of the i^th-from-last string), + // because if we don't we will mutate some strings multiple times due to the in-place nature of mutation + "for s = zip(strs, strs_cpy, strs_cpy[::-1]);\r\n" + + " s[0] = s[1] + s[2];\r\n" + + "end for" + }), + ("compare_text", new object[]{"[\r\n \"0.25 is champion!0.75 is champion!\",\r\n \"0.75 is champion!0.25 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!1.25 is champion!\"\r\n][\r\n \"boo is champion!c\",\r\n \"aa\",\r\n \"cboo is champion!\"\r\n]"}), + // TEST REGEX SEARCH FORM (REGEX MODE) + ("file_open", new object[]{2, "txt"}), + ("overwrite", new object[]{"\u200a\u202f\n foo: 1\nBA\u042f: \u00a0-3\nbaz: +85"}), + ("regex_search", new object[]{ + "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(INT)\",\"ignoreCase\":true,\"fullMatch\":false,\"numCols\":[1]}" + }), + ("treenode_click", new object[]{new string[] {"0 : [2]"} }), + ("compare_selections", new object[]{new string[] {"7,7"} }), + ("treenode_click", new object[]{new string[] {"1 : [2]", "1 : -3"} }), + ("compare_selections", new object[]{new string[] {"23,23"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"23,25"} }), + ("treenode_click", new object[]{new string[] {"2 : [2]", "1 : 85"} }), + ("compare_selections", new object[]{new string[] {"31,31"} }), + ("regex_search", new object[]{ + "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(NUMBER)\",\"ignoreCase\":false,\"fullMatch\":true}" + }), + ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"baz: +85\""} }), + ("treenode_click", new object[]{new string[] {"0 : [3]", "1 : \"foo\""} }), + ("treenode_click", new object[]{new string[] {"0 : [3]", "2 : \"1\""} }), + ("tree_query", new object[]{ + "@ = s_sub(@,\r\n" + + " g`(?i)^\\x20*([a-zЯ]+):\\s+(INT)`,\r\n" + + " ifelse(float(@[2]) < 0,\r\n" + + " str(loop()) + @[0],\r\n" + + " str(loop()) + `. ` + @[1] + ` (` + str(int(@[2])) + `)`\r\n" + + " )\r\n" + + ")"}), + ("compare_text", new object[]{"\u200a\u202f\n1. foo (1)\n2BA\u042f: \u00a0-3\n3. baz (85)"}), + // TEST REGEX SEARCH FORM (CSV MODE) + ("overwrite", new object[]{ + "ab\tc\t😀\tf\r\n" + + "'fo\nö'\t7\tbar\t-8.5\r\n" + + "baz\t19\t''\t-4e3\r\n" + + "'zorq'\t\tkywq\t'.75'" + }), + ("regex_search", new object[] + { + "{\"csv\":true,\"delim\":\"\\\\t\",\"quote\":\"'\",\"newline\":\"\\r\\n\",\"header\":\"d\",\"nColumns\":4,\"numCols\":[-1,1]}" + }), + ("treenode_click", new object[]{new string[]{"0 : {4}", "ab : \"fo\\nö\"" } }), + ("treenode_click", new object[]{new string[]{"1 : {4}", "c : 19"} }), + ("treenode_click", new object[]{new string[]{"1 : {4}", "😀 : \"\"" } }), + ("treenode_click", new object[]{new string[]{"2 : {4}", "f : \".75\""} }), + ("compare_selections", new object[]{new string[] {"62,62"} }), + ("regex_search", new object[] + { + "{\"csv\": true, \"delim\": \"\\\\t\", \"quote\": \"'\", \"newline\": \"\\r\\n\", \"header\": 1, \"nColumns\": 4, \"numCols\": [1]}" + }), + ("treenode_click", new object[]{new string[] {"0 : [4]", "3 : \"f\""} }), + ("treenode_click", new object[]{new string[] {"1 : [4]", "1 : 7"} }), + ("treenode_click", new object[]{new string[] {"2 : [4]", "3 : \"-4e3\""} }), + ("treenode_click", new object[]{new string[] {"3 : [4]", "0 : \"zorq\""} }), + // TEST SELECT ALL CHILDREN IN CSV FILE + ("tree_query", new object[]{"s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h)[:][0]"}), + ("treenode_click", new object[]{new string[]{ } }), // click root + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"0,2", "13,20", "33,36", "49,55"} }), + ("treenode_click", new object[]{new string[]{ "1 : \"fo\\nö\"" } }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"13,20"} }), + // MUTATE CSV FILE WITH QUERY + ("tree_query", new object[]{ + "var c = s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h, 1);\r\n" + + "c[:][1][is_num(@)] = @/4;\r\n" + + "@ = to_csv(c,`,`, `\\n`, `\"`)"}), + ("compare_text", new object[]{"ab,c,😀,f\n\"fo\nö\",1.75,bar,-8.5\nbaz,4.75,,-4e3\nzorq,,kywq,.75\n"}), + // TEST REGEX SEARCH FORM (SINGLE-CAPTURE-GROUP CSV AND REGEX MODES) + ("overwrite", new object[]{" 草1\nェ2\n◐3\n'4"}), // last row has CSV quote char to make sure CSV delimiter is forgotten now that a non-csv search was made + ("regex_search", new object[]{ + "{\"csv\":false,\"regex\":\"^\\\\S\\\\d\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}" + }), + ("treenode_click", new object[]{new string[]{ } }), // click root + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"6,10", "11,15", "16,18"} }), + ("treenode_click", new object[]{new string[] { "1 : \"◐3\"" } }), + ("compare_selections", new object[]{new string[] {"11,11"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"11,15"} }), + ("regex_search", new object[]{ + "{\"csv\":false,\"regex\":\"^\\\\S(\\\\d)\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}" // this time capture just the number after the nonspace + }), + ("treenode_click", new object[]{new string[] { "1 : \"3\"" } }), + ("compare_selections", new object[]{new string[] {"14,14"} }), + ("regex_search", new object[] + { + "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": \"\\n\", \"header\": 0, \"nColumns\": 1}" + }), + ("treenode_click", new object[]{new string[] { "0 : \"ェ2\"" } }), + ("compare_selections", new object[]{new string[] {"6,6"} }), + ("regex_search", new object[] + { + "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": 1, \"header\": 2, \"nColumns\": 1}" + }), + ("treenode_click", new object[]{new string[] { "0 : {1}", " 草1 : \"ェ2\"" } }), + ("compare_selections", new object[]{new string[] {"6,6"} }), + // TEST REGEX SEARCH ON FILE WITH MULTIPLE SELECTIONS + ("overwrite", new object[]{"1 2\r[3]ö4\r\"\u00a0\r5 6\r'foo'"}), + ("tree_query", new object[]{"s_csv(@, 1, `,`, `\\r`, `\"`, h)"}), + ("treenode_click", new object[]{new string[] {} }), + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"0,3", "4,10", "15,18", "19,24"} }), + ("tree_open", new object[]{}), + ("tree_open", new object[]{}), + ("set_document_type", new object[]{ "REGEX" }), // re-parse document in multi-selection mode + ("treenode_click", new object[]{new string[]{ "15,18 : \"5 6\"" } }), + ("compare_selections", new object[]{new string[] {"15,15"} }), + ("tree_query", new object[]{"s_fa(@, g`\\d+`,, 0)"}), + ("treenode_click", new object[]{ new string[] { "4,10 : [2]", "1 : 4" } }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[]{"9,10" } }), + ("treenode_click", new object[]{ new string[] { "15,18 : [2]" } }), + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[]{"15,16", "17,18" } }), + // TEST REGEX EDITING ON FILE WITH MULTIPLE SELECTIONS + ("select", new object[]{new string[] {"0,0"} }), + ("tree_query", new object[]{ "@ = s_sub(@, g`(INT)`, str(int(@[0])->ifelse(@ > 3, -@, @)))" }), + ("compare_text", new object[]{"1 2\r[3]ö-4\r\"\u00a0\r-5 -6\r'foo'" }), + ("compare_selections", new object[]{new string[] {"0,3", "4,11", "16,21", "22,27"} }), + // TEST DOCUMENT TYPE CHANGE BUTTON IN TREEVIEW + ("overwrite", new object[]{"[1, 2]\r\n{\"ö\": \"3 4\"}\r\n\"\"\r\n{\"5\": [6]}\r\n'foo'"}), + ("set_document_type", new object[]{"JSON"}), + ("treenode_click", new object[]{new string[] {"0 : 1"} }), + ("treenode_click", new object[]{new string[] {"1 : 2"} }), + ("set_document_type", new object[]{"JSONL"}), + ("treenode_click", new object[]{new string[] {"3 : {1}", "5 : [1]", "0 : 6"} }), + ("compare_selections", new object[]{new string[] {"34,34"} }), + ("treenode_click", new object[]{new string[] {"4 : \"foo\""} }), + ("set_document_type", new object[]{"REGEX"}), + ("tree_query", new object[]{"s_csv(@, 1,`\\t`,`\\r\\n`,`'` ,h)"}), + ("treenode_click", new object[]{new string[] { "1 : \"{\\\"ö\\\": \\\"3 4\\\"}\"" } }), + ("treenode_click", new object[]{new string[] { "3 : \"{\\\"5\\\": [6]}\"" } }), + }; - public static bool Test() - { var messages = new List(); int failures = 0; string previouslyOpenFname = Npp.notepad.GetCurrentFilePath(); @@ -735,30 +786,26 @@ public static bool Test() // this message box doesn't block the main thread, but it introduces some asynchronous behavior // that was probably responsible for crashing the UI tests Main.hasWarnedSelectionsForgotten = true; - if (!hasRunTestsThisSession) + // add command to overwrite with a lot of arrays and select every valid json + try { - hasRunTestsThisSession = true; - // add command to overwrite with a lot of arrays and select every valid json - try - { - string oneArray2000xStr = (string)remesParser.Search("@ * 2000", new JNode("[1]\r\n")).value; - JArray oneArray2000x = (JArray)jsonParser.ParseJsonLines(oneArray2000xStr); - string[] oneArray2000xSelections = ((JArray)remesParser.Search("(range(2000) * 5)[:]{@, @ + 3}{s_join(`,`, str(@))}[0]", new JNode())).children - .Select(x => (string)x.value) - .ToArray(); - string[] oneArray2000xPPrintSelections = ((JArray)remesParser.Search("(range(2000) * 13)[:]{@, @ + 11}{s_join(`,`, str(@))}[0]", new JNode())).children - .Select(x => (string)x.value) - .ToArray(); - testcases.Add(("overwrite", new object[] { oneArray2000xStr })); - testcases.Add(("select_every_valid", new object[] { })); - testcases.Add(("compare_selections", new object[] { oneArray2000xSelections })); - testcases.Add(("pretty_print", new object[] { })); - testcases.Add(("compare_selections", new object[] { oneArray2000xPPrintSelections })); - } - catch (Exception ex) - { - messages.Add($"Failed to add testcases due to exception {ex}"); - } + string oneArray2000xStr = (string)remesParser.Search("@ * 2000", new JNode("[1]\r\n")).value; + JArray oneArray2000x = (JArray)jsonParser.ParseJsonLines(oneArray2000xStr); + string[] oneArray2000xSelections = ((JArray)remesParser.Search("(range(2000) * 5)[:]{@, @ + 3}{s_join(`,`, str(@))}[0]", new JNode())).children + .Select(x => (string)x.value) + .ToArray(); + string[] oneArray2000xPPrintSelections = ((JArray)remesParser.Search("(range(2000) * 13)[:]{@, @ + 11}{s_join(`,`, str(@))}[0]", new JNode())).children + .Select(x => (string)x.value) + .ToArray(); + testcases.Add(("overwrite", new object[] { oneArray2000xStr })); + testcases.Add(("select_every_valid", new object[] { })); + testcases.Add(("compare_selections", new object[] { oneArray2000xSelections })); + testcases.Add(("pretty_print", new object[] { })); + testcases.Add(("compare_selections", new object[] { oneArray2000xPPrintSelections })); + } + catch (Exception ex) + { + messages.Add($"Failed to add testcases due to exception {ex}"); } // run all the commands int lastFailureIndex = messages.Count; diff --git a/docs/README.md b/docs/README.md index f000b11..714b31c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -115,6 +115,26 @@ When you parse a document that contains syntax errors, you may be asked if you w In [v5.3.0](/CHANGELOG.md#530---2023-06-10), a form was added to display errors. Prior to that, errors were shown as text in a new buffer. +### Document type box *(added in v6.0)* ### + +*Beginning in version [v6.0](/CHANGELOG.md#600---unreleased-2023-mm-dd),* the tree view has a document type box just above the tree itself. + +This box has four options (auto): +* `JSON mode`: parse document (or each selection) as JSON +* `JSONL mode`: parse document (or each selection) as [JSON lines](#json-lines-documents) +* `INI mode`: parse document (or each selection) as an [INI file](#parsing-ini-files) +* `REGEX mode`: the document (or each selection) is converted to a JSON string containing its text, which can then be [searched and edited with regex and RemesPath](#regex-search-form). + +Observe the three images below to see how the selected box causes the same document to be interpreted in three different ways (`INI mode` not shown, because that's not a valid INI file). + +![Document type box - JSON mode example](/docs/document%20type%20box%20example%20-%20JSON%20mode.PNG) + +![Document type box - JSON Lines mode example](/docs/document%20type%20box%20example%20-%20JSONL%20mode.PNG) + +![Document type box - REGEX mode example](/docs/document%20type%20box%20example%20-%20REGEX%20mode.PNG) + +While this box is focused, you can scroll through these options, or navigate to a different option by typing the first key of its name. + ### Working with selections ### [Starting in version v5.5](/CHANGELOG.md#550---2023-08-13), you can work with one or more selections rather than treating the entire file as JSON. diff --git a/docs/RemesPath.md b/docs/RemesPath.md index 41eac20..37f7864 100644 --- a/docs/RemesPath.md +++ b/docs/RemesPath.md @@ -516,6 +516,19 @@ Returns an array of integers. * `range(3, 1, -1)` returns `[3, 2]`. * `range(0, 6, 3)` returns `[0, 3]`. +--- +`set(x: array) -> object` + +*Added in [v6.0](/CHANGELOG.md#600---unreleased-2023-mm-dd)* + +Returns an object mapping each unique string representation of an element in `x` to null. This may be preferable to `unique` because of the O(1) average-case lookup performance in an object. + +Example: ``set(j`["a", "b", "a", 1, 2.0, null, 1, null]`)`` returns `{"a": null, "b": null, "1": null, "2.0": null, "null": null}` + +Issues with this function that may make the `unique` function preferable: +* two different elements may have the same string representation for the purposes of this function (e.g., `null` and `"null"`, `2.0` and `"2.0"`) +* strings with characters that must be escaped (e.g., `\`, `"`) have those characters escaped before they become keys in the object returned by `set` + ---- `s_join(sep: string, x: array) -> string` @@ -524,11 +537,16 @@ Every element of `x` must be a string. Returns x string-joined with sep (i.e., returns a string that begins with `x[0]` and has `sep` between `x[i - 1]` and `x[i]` for `1 <= i <= len(x)`) ---- -`sort_by(x: array, k: string | int, descending: bool = false)` +`sort_by(x: array, k: string | int | function, descending: bool = false)` -`x` must be an array of arrays (in which case `k` must be an int) or an array of objects (in which case `k` must be a string). +`x` must be: +* an array of arrays (if `k` is an integer) +* an array of objects (if `k` is a string) +* any array (if `k` is a function) -Returns a new array of subarrays/subobjects `subitbl` such that `subitbl[k]` is sorted ascending. +Returns: +* a new array of subarrays/subobjects `subitbl` such that `subitbl[k]` is sorted (if `k` is an integer or string) +* a new array of children `child` such that `k(child)` is sorted (if `k` is a function) Analogous to SQL `ORDER BY`. @@ -538,8 +556,8 @@ Prior to [v5.5.0](/CHANGELOG.md#550---2023-08-13), Python-style negative indices __Examples:__ * With `[[1, 2], [2, 0], [3, -1]]` as input, `sort_by(@, 1)` returns `[[3,-1],[2,0],[1,2]]` because it sorts ascending by the second element. -* With `[{"a": 1, "b": 3}, {"a": 2, "b": 2}, {"a": 3, "b": 1}]` as input, `sort_by(@, a, true)` returns `[{"a":3,"b":1},{"a":2,"b":2},{"a":1,"b":3}]` because it sorts ascending by key `a`. -* With `["a", "bbb", "cc"]` as input, `sort_by(@, s_len(@))` returns `["a", "cc", "bbb"]`, because the children are ordered by string length ascending +* With `[{"a": 1, "b": 3}, {"a": 2, "b": 2}, {"a": 3, "b": 1}]` as input, `sort_by(@, a, true)` returns `[{"a":3,"b":1},{"a":2,"b":2},{"a":1,"b":3}]` because it sorts descending by key `a`. +* With `["a", "bbb", "cc"]` as input, `sort_by(@, s_len(@))` returns `["a", "cc", "bbb"]`, because the children are sorted ascending by string length. ---- `sorted(x: array, descending: bool = false)` diff --git a/docs/document type box example - JSON mode.PNG b/docs/document type box example - JSON mode.PNG new file mode 100644 index 0000000000000000000000000000000000000000..ff5ab0fb4110762a647ee45fb1cf760dd5658786 GIT binary patch literal 12741 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 M?TTuS^7Z@w2R5FGqW}N^ literal 0 HcmV?d00001 diff --git a/docs/document type box example - JSONL mode.PNG b/docs/document type box example - JSONL mode.PNG new file mode 100644 index 0000000000000000000000000000000000000000..a2bfe93a34606aab43f3f2f64f4edebf0d7ee800 GIT binary patch literal 15228 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 literal 0 HcmV?d00001 diff --git a/docs/document type box example - REGEX mode.PNG b/docs/document type box example - REGEX mode.PNG new file mode 100644 index 0000000000000000000000000000000000000000..27b3ed38692810623c359e494660c5f701b6dec0 GIT binary patch literal 11702 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 M%AFe}O6CFo59&1_&j0`b literal 0 HcmV?d00001 diff --git a/most recent errors.txt b/most recent errors.txt index 7034631..b3ef6bb 100644 --- a/most recent errors.txt +++ b/most recent errors.txt @@ -1,4 +1,4 @@ -Test results for JsonTools v5.8.0.15 on Notepad++ 8.5.8 64bit +Test results for JsonTools v5.8.0.17 on Notepad++ 8.5.8 64bit NOTE: Ctrl-F (regular expressions *on*) for "Failed [1-9]\d*" to find all failed tests Tests failed: YAML dumper ========================= @@ -114,7 +114,7 @@ Testing RemesPath parser and compiler The queried JSON in the RemesParser tests is named foo:{"foo": [[0, 1, 2], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]], "bar": {"a": false, "b": ["a`g", "bah"]}, "baz": "z", "quz": {}, "jub": [], "guzo": [[[1]], [[2], [3]]], "7": [{"foo": 2}, 1], "_": {"0": 0}} Failed 0 tests. -Passed 453 tests. +Passed 454 tests. ========================= Testing RemesPath throws errors on bad inputs ========================= @@ -133,7 +133,7 @@ Testing RemesPath produces sane outputs on randomly generated queries Fuzz tests query {"a": [-4, -3., -2., -1, 0, 1, 2., 3., 4], "bc": NaN,"c`d": "df", "q": ["", "a", "jk", "ian", "", "32", "u", "aa", "moun"],"f": 1,"g": 1,"h": 1,"i": 1,"j": 1} -Ran 10000 fuzz tests +Ran 5000 fuzz tests Failed 0 fuzz tests ========================= Testing multi-statement queries in RemesPath @@ -195,40 +195,40 @@ Testing UI tests ========================= Failed 0 tests -Passed 298 tests +Passed 330 tests ========================= Testing JsonParser performance ========================= Preview of json: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c" ... -To convert JSON string of size 89556 into JNode took 2.705 +/- 1.982 ms over 32 trials -Load times (ms): 2, 2, 1, 3, 1, 2, 5, 1, 1, 1, 3, 1, 1, 4, 1, 1, 11, 4, 1, 1, 4, 1, 1, 1, 2, 4, 1, 2, 2, 2, 2, 1 +To convert JSON string of size 89556 into JNode took 2.432 +/- 1.576 ms over 32 trials +Load times (ms): 1, 2, 9, 1, 2, 3, 1, 1, 2, 3, 1, 1, 4, 1, 1, 1, 2, 1, 1, 4, 1, 1, 1, 3, 1, 1, 4, 1, 1, 1, 3, 1 ========================= Performance tests for RemesPath (float arithmetic) ========================= -Compiling query "@[@[:].a * @[:].t < @[:].e]" into took 2.482 +/- 15.103 microseconds over 40 trials -To run pre-compiled query "@[@[:].a * @[:].t < @[:].e]" on JNode from JSON of size 89556 into took 0.029 +/- 0.013 ms over 40 trials -Query times (ms): 0.058, 0.058, 0.023, 0.023, 0.023, 0.023, 0.023, 0.025, 0.023, 0.023, 0.023, 0.023, 0.023, 0.024, 0.024, 0.023, 0.023, 0.023, 0.023, 0.024, 0.023, 0.023, 0.023, 0.022, 0.023, 0.039, 0.074, 0.04, 0.035, 0.025, 0.034, 0.075, 0.024, 0.024, 0.034, 0.023, 0.023, 0.028, 0.025, 0.024 +Compiling query "@[@[:].a * @[:].t < @[:].e]" into took 2.335 +/- 14.15 microseconds over 40 trials +To run pre-compiled query "@[@[:].a * @[:].t < @[:].e]" on JNode from JSON of size 89556 into took 0.115 +/- 0.557 ms over 40 trials +Query times (ms): 0.07, 0.036, 0.025, 0.023, 0.023, 0.023, 0.023, 0.029, 0.023, 0.024, 0.023, 0.023, 0.023, 0.028, 0.023, 0.023, 0.023, 0.024, 0.023, 0.026, 0.023, 0.024, 0.023, 0.023, 0.024, 0.026, 0.023, 0.023, 0.024, 0.023, 0.023, 0.025, 0.024, 0.023, 0.023, 0.023, 0.024, 0.025, 0.024, 3.594 Preview of result: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c" ... ========================= Performance tests for RemesPath (string operations) ========================= -Compiling query "@[@[:].z =~ `(?i)[a-z]{5}`]" into took 2.975 +/- 17.954 microseconds over 40 trials -To run pre-compiled query "@[@[:].z =~ `(?i)[a-z]{5}`]" on JNode from JSON of size 89556 into took 0.112 +/- 0.02 ms over 40 trials -Query times (ms): 0.191, 0.165, 0.112, 0.114, 0.112, 0.116, 0.104, 0.111, 0.102, 0.107, 0.128, 0.113, 0.109, 0.111, 0.112, 0.111, 0.111, 0.113, 0.149, 0.112, 0.095, 0.098, 0.106, 0.098, 0.103, 0.107, 0.119, 0.106, 0.105, 0.103, 0.108, 0.123, 0.115, 0.116, 0.104, 0.121, 0.106, 0.111, 0.072, 0.064 +Compiling query "@[@[:].z =~ `(?i)[a-z]{5}`]" into took 2.068 +/- 12.431 microseconds over 40 trials +To run pre-compiled query "@[@[:].z =~ `(?i)[a-z]{5}`]" on JNode from JSON of size 89556 into took 0.055 +/- 0.01 ms over 40 trials +Query times (ms): 0.111, 0.055, 0.054, 0.054, 0.082, 0.054, 0.053, 0.054, 0.054, 0.053, 0.051, 0.052, 0.052, 0.052, 0.051, 0.052, 0.052, 0.052, 0.052, 0.051, 0.052, 0.051, 0.052, 0.052, 0.052, 0.052, 0.052, 0.053, 0.052, 0.053, 0.052, 0.053, 0.052, 0.052, 0.052, 0.052, 0.052, 0.053, 0.052, 0.052 Preview of result: [{"A": "\n]o1VQ5t6g", "a": 4710024278, "b": 3268860721, "B": "g4Y7+ew^.v", "C": "NK nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" into took 5.272 +/- 31.406 microseconds over 40 trials +ifelse(nmax_q > nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" into took 4.99 +/- 29.866 microseconds over 40 trials To run pre-compiled query "var qmask = @[:].q; var nmax_q = max(@[qmask].n); var nmax_notq = max(@[not qmask].n); -ifelse(nmax_q > nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" on JNode from JSON of size 89556 into took 0.033 +/- 0.042 ms over 40 trials -Query times (ms): 0.088, 0.027, 0.028, 0.025, 0.018, 0.048, 0.018, 0.017, 0.017, 0.287, 0.026, 0.026, 0.024, 0.025, 0.025, 0.023, 0.024, 0.025, 0.025, 0.032, 0.026, 0.016, 0.016, 0.016, 0.021, 0.023, 0.024, 0.04, 0.025, 0.025, 0.026, 0.025, 0.024, 0.026, 0.024, 0.025, 0.025, 0.025, 0.025, 0.026 +ifelse(nmax_q > nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" on JNode from JSON of size 89556 into took 0.025 +/- 0.031 ms over 40 trials +Query times (ms): 0.076, 0.026, 0.028, 0.038, 0.024, 0.048, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.206, 0.031, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.015, 0.015, 0.016, 0.015, 0.016 Preview of result: "when q=false, nmax= 9830935647.0" ... ========================= @@ -267,11 +267,11 @@ Performance tests for RemesPath (references to compile-time constant variables) Compiling query "var X = X; var onetwo = j`[1, 2]`; -@[:]->at(@, X)->at(@, onetwo)" into took 3.157 +/- 19.094 microseconds over 40 trials +@[:]->at(@, X)->at(@, onetwo)" into took 2.65 +/- 15.941 microseconds over 40 trials To run pre-compiled query "var X = X; var onetwo = j`[1, 2]`; -@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.016 +/- 0.01 ms over 40 trials -Query times (ms): 0.054, 0.026, 0.016, 0.02, 0.013, 0.012, 0.013, 0.013, 0.015, 0.05, 0.028, 0.013, 0.012, 0.013, 0.039, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.012, 0.013, 0.012, 0.012, 0.013, 0.013, 0.012 +@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.014 +/- 0.005 ms over 40 trials +Query times (ms): 0.038, 0.013, 0.013, 0.012, 0.012, 0.012, 0.012, 0.012, 0.013, 0.03, 0.012, 0.012, 0.013, 0.012, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.012, 0.012, 0.012, 0.013, 0.012, 0.013, 0.012, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.012, 0.013, 0.012, 0.012, 0.012, 0.012 Preview of result: [[1695727848, 0.287562638736685], [2126430375, 0.00767794129708177], [5310550656, 0.380769772645687], [2519183283, 0.153176220930558], [6610062385, 0.662996225870666], [987168256, 0.924410189999928], [6615003609, 0.917112691225947], [4465232046, 0.684311931851536], [8654414565, 0.631485392105992], [ ... ========================= @@ -280,29 +280,29 @@ Performance tests for RemesPath (references to variables that are not compile-ti Compiling query "var X = @->`X`; var onetwo = @{1, 2}; -@[:]->at(@, X)->at(@, onetwo)" into took 4.12 +/- 24.849 microseconds over 40 trials +@[:]->at(@, X)->at(@, onetwo)" into took 2.662 +/- 15.939 microseconds over 40 trials To run pre-compiled query "var X = @->`X`; var onetwo = @{1, 2}; -@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.031 +/- 0.011 ms over 40 trials -Query times (ms): 0.091, 0.029, 0.029, 0.028, 0.028, 0.04, 0.028, 0.058, 0.028, 0.028, 0.028, 0.028, 0.027, 0.028, 0.028, 0.027, 0.028, 0.028, 0.027, 0.028, 0.028, 0.028, 0.028, 0.028, 0.027, 0.029, 0.027, 0.028, 0.028, 0.028, 0.028, 0.034, 0.028, 0.027, 0.029, 0.028, 0.028, 0.028, 0.028, 0.027 +@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.017 +/- 0.005 ms over 40 trials +Query times (ms): 0.051, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.017, 0.017, 0.016, 0.016 Preview of result: [[1695727848, 0.287562638736685], [2126430375, 0.00767794129708177], [5310550656, 0.380769772645687], [2519183283, 0.153176220930558], [6610062385, 0.662996225870666], [987168256, 0.924410189999928], [6615003609, 0.917112691225947], [4465232046, 0.684311931851536], [8654414565, 0.631485392105992], [ ... ========================= Performance tests for RemesPath (simple string mutations) ========================= -Compiling query "@[:].z = s_sub(@, g, B)" into took 2.535 +/- 15.319 microseconds over 40 trials -To run pre-compiled query "@[:].z = s_sub(@, g, B)" on JNode from JSON of size 89556 into took 0.025 +/- 0.007 ms over 40 trials -Query times (ms): 0.054, 0.025, 0.022, 0.022, 0.021, 0.024, 0.027, 0.026, 0.022, 0.031, 0.022, 0.02, 0.021, 0.021, 0.019, 0.024, 0.022, 0.023, 0.026, 0.023, 0.024, 0.023, 0.024, 0.03, 0.021, 0.024, 0.02, 0.02, 0.021, 0.02, 0.02, 0.035, 0.032, 0.035, 0.022, 0.028, 0.031, 0.032, 0.016, 0.015 +Compiling query "@[:].z = s_sub(@, g, B)" into took 1.79 +/- 10.842 microseconds over 40 trials +To run pre-compiled query "@[:].z = s_sub(@, g, B)" on JNode from JSON of size 89556 into took 0.014 +/- 0.007 ms over 40 trials +Query times (ms): 0.024, 0.011, 0.011, 0.01, 0.009, 0.01, 0.011, 0.01, 0.01, 0.01, 0.014, 0.013, 0.014, 0.013, 0.015, 0.012, 0.011, 0.012, 0.012, 0.01, 0.012, 0.011, 0.011, 0.011, 0.01, 0.01, 0.014, 0.013, 0.016, 0.01, 0.011, 0.011, 0.011, 0.011, 0.013, 0.044, 0.029, 0.025, 0.024, 0.015 Preview of result: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c" ... ========================= Performance tests for RemesPath (simple number mutations) ========================= -Compiling query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" into took 2.815 +/- 16.955 microseconds over 40 trials -To run pre-compiled query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" on JNode from JSON of size 89556 into took 0.09 +/- 0.36 ms over 40 trials -Query times (ms): 0.044, 0.029, 0.026, 0.024, 0.025, 0.026, 0.051, 0.034, 0.04, 0.027, 0.024, 0.027, 0.026, 0.026, 0.024, 0.023, 0.024, 0.024, 0.034, 0.032, 0.066, 0.044, 0.027, 0.026, 0.026, 0.024, 0.025, 0.025, 0.023, 0.024, 0.023, 0.024, 0.03, 0.088, 2.334, 0.047, 0.034, 0.038, 0.036, 0.035 +Compiling query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" into took 18.682 +/- 116.032 microseconds over 40 trials +To run pre-compiled query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" on JNode from JSON of size 89556 into took 0.027 +/- 0.009 ms over 40 trials +Query times (ms): 0.036, 0.022, 0.021, 0.021, 0.02, 0.021, 0.021, 0.02, 0.021, 0.02, 0.02, 0.02, 0.029, 0.025, 0.021, 0.042, 0.03, 0.057, 0.047, 0.029, 0.022, 0.021, 0.022, 0.021, 0.028, 0.027, 0.026, 0.021, 0.021, 0.02, 0.021, 0.042, 0.042, 0.038, 0.042, 0.026, 0.025, 0.027, 0.028, 0.025 Preview of result: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c" ... ========================= @@ -312,12 +312,12 @@ Performance tests for RemesPath (mutations with a for loop) Compiling query "var xhalf = @[:].x < 0.5; for lx = zip(@[:].l, xhalf); lx[0] = ifelse(lx[1], foo, bar); -end for;" into took 6.272 +/- 38.195 microseconds over 40 trials +end for;" into took 4.422 +/- 26.802 microseconds over 40 trials To run pre-compiled query "var xhalf = @[:].x < 0.5; for lx = zip(@[:].l, xhalf); lx[0] = ifelse(lx[1], foo, bar); -end for;" on JNode from JSON of size 89556 into took 0.064 +/- 0.023 ms over 40 trials -Query times (ms): 0.092, 0.101, 0.116, 0.053, 0.063, 0.059, 0.087, 0.063, 0.062, 0.063, 0.064, 0.058, 0.057, 0.034, 0.038, 0.085, 0.085, 0.067, 0.062, 0.049, 0.044, 0.043, 0.043, 0.043, 0.041, 0.055, 0.07, 0.049, 0.05, 0.051, 0.049, 0.091, 0.068, 0.062, 0.061, 0.102, 0.14, 0.052, 0.038, 0.051 +end for;" on JNode from JSON of size 89556 into took 0.053 +/- 0.023 ms over 40 trials +Query times (ms): 0.061, 0.038, 0.037, 0.037, 0.036, 0.036, 0.037, 0.035, 0.049, 0.09, 0.081, 0.055, 0.04, 0.039, 0.043, 0.044, 0.04, 0.041, 0.04, 0.04, 0.04, 0.042, 0.089, 0.066, 0.044, 0.043, 0.038, 0.044, 0.037, 0.034, 0.035, 0.141, 0.089, 0.06, 0.053, 0.056, 0.065, 0.071, 0.061, 0.099 Preview of result: [["bar", false], ["bar", false], ["foo", true], ["foo", true], ["foo", true], ["foo", true], ["foo", true], ["bar", false], ["bar", false], ["bar", false], ["foo", true], ["foo", true], ["bar", false], ["bar", false], ["foo", true], ["bar", false], ["bar", false], ["bar", false], ["foo", true], ["ba ... ========================= @@ -326,18 +326,18 @@ Testing performance of JSON compression and pretty-printing Preview of json: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c" ... -To compress JNode from JSON string of 89556 took 3.984 +/- 0.471 ms over 64 trials (minimal whitespace, sort_keys=TRUE) -To compress JNode from JSON string of 89556 took 2.148 +/- 0.475 ms over 64 trials (minimal whitespace, sort_keys=FALSE) -To Google-style pretty-print JNode from JSON string of 89556 took 4.269 +/- 0.549 ms over 64 trials (sort_keys=true, indent=4) -To Whitesmith-style pretty-print JNode from JSON string of 89556 took 4.328 +/- 0.448 ms over 64 trials (sort_keys=true, indent=4) -To PPrint-style pretty-print JNode from JSON string of 89556 took 7.543 +/- 1.133 ms over 64 trials (sort_keys=true, indent=4) +To compress JNode from JSON string of 89556 took 3.923 +/- 0.473 ms over 64 trials (minimal whitespace, sort_keys=TRUE) +To compress JNode from JSON string of 89556 took 2.144 +/- 0.267 ms over 64 trials (minimal whitespace, sort_keys=FALSE) +To Google-style pretty-print JNode from JSON string of 89556 took 4.823 +/- 0.912 ms over 64 trials (sort_keys=true, indent=4) +To Whitesmith-style pretty-print JNode from JSON string of 89556 took 4.274 +/- 0.587 ms over 64 trials (sort_keys=true, indent=4) +To PPrint-style pretty-print JNode from JSON string of 89556 took 6.174 +/- 0.485 ms over 64 trials (sort_keys=true, indent=4) ========================= Testing performance of JsonSchemaValidator and random JSON creation ========================= -To create a random set of tweet JSON of size 140467 (15 tweets) based on the matching schema took 6.966 +/- 3.393 ms over 64 trials -To compile the tweet schema to a validation function took 0.232 +/- 0.057 ms over 64 trials -To validate tweet JSON of size 140467 (15 tweets) based on the compiled schema took 1.13 +/- 0.276 ms over 64 trials +To create a random set of tweet JSON of size 169566 (15 tweets) based on the matching schema took 6.868 +/- 3.266 ms over 64 trials +To compile the tweet schema to a validation function took 0.389 +/- 0.786 ms over 64 trials +To validate tweet JSON of size 169566 (15 tweets) based on the compiled schema took 1.064 +/- 0.172 ms over 64 trials ========================= Testing JSON grepper's API request tool =========================