diff --git a/MongoView.xcodeproj/project.pbxproj b/MongoView.xcodeproj/project.pbxproj index 7e2d822..f2c5b94 100644 --- a/MongoView.xcodeproj/project.pbxproj +++ b/MongoView.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ D627CE8824E399F100C39FE5 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627CE8624E399F100C39FE5 /* DetailViewController.swift */; }; D627CE8924E399F100C39FE5 /* DetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627CE8724E399F100C39FE5 /* DetailViewController.xib */; }; D627CE8B24E438EE00C39FE5 /* DatabaseCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627CE8A24E438EE00C39FE5 /* DatabaseCollection.swift */; }; + D627CE8D24E4478800C39FE5 /* JSONPrettyPrinter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627CE8C24E4478800C39FE5 /* JSONPrettyPrinter.swift */; }; D63CDEBE23C837DC0012D658 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDEBD23C837DC0012D658 /* AppDelegate.swift */; }; D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D63CDEBF23C837DD0012D658 /* Assets.xcassets */; }; D63CDEC323C837DD0012D658 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDEC123C837DD0012D658 /* MainMenu.xib */; }; @@ -107,6 +108,7 @@ D627CE8624E399F100C39FE5 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; D627CE8724E399F100C39FE5 /* DetailViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DetailViewController.xib; sourceTree = ""; }; D627CE8A24E438EE00C39FE5 /* DatabaseCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseCollection.swift; sourceTree = ""; }; + D627CE8C24E4478800C39FE5 /* JSONPrettyPrinter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONPrettyPrinter.swift; sourceTree = ""; }; D63CDEBA23C837DC0012D658 /* MongoView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MongoView.app; sourceTree = BUILT_PRODUCTS_DIR; }; D63CDEBD23C837DC0012D658 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; D63CDEBF23C837DD0012D658 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -234,6 +236,7 @@ D627CE8A24E438EE00C39FE5 /* DatabaseCollection.swift */, D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */, D624090E243903E90020E09F /* ExtendedJSON.swift */, + D627CE8C24E4478800C39FE5 /* JSONPrettyPrinter.swift */, D6A7D0A22435880700B46857 /* Synax Highlighting */, D60C863B23CA2DD600C9DB8E /* Windows */, D60C863C23CA2DDD00C9DB8E /* View Controllers */, @@ -383,6 +386,7 @@ D63CDEBE23C837DC0012D658 /* AppDelegate.swift in Sources */, D6D4665323CB730C00F13B1B /* MongoEvaluator.swift in Sources */, D624090F243903E90020E09F /* ExtendedJSON.swift in Sources */, + D627CE8D24E4478800C39FE5 /* JSONPrettyPrinter.swift in Sources */, D63CDF3823C8381A0012D658 /* MongoController.swift in Sources */, D60C863923CA2DD100C9DB8E /* ServerConnectWindowController.swift in Sources */, D63CDF3723C8381A0012D658 /* Node.swift in Sources */, diff --git a/MongoView/ExtendedJSON.swift b/MongoView/ExtendedJSON.swift index 7679858..9b706b5 100644 --- a/MongoView/ExtendedJSON.swift +++ b/MongoView/ExtendedJSON.swift @@ -19,6 +19,10 @@ struct ExtendedJSON { return ["$oid": id] } context.setObject(objectId, forKeyedSubscript: "ObjectId" as NSString) + let date: @convention(block) (String) -> [String: String] = { (date) in + return ["$date": date] + } + context.setObject(date, forKeyedSubscript: "Date" as NSString) return context }() @@ -44,10 +48,4 @@ struct ExtendedJSON { return doc } - static func prettify(_ string: String) -> String? { - context.setObject(string, forKeyedSubscript: "value" as NSString) - let command = "JSON.stringify(JSON.parse(value), null, 4)" - return context.evaluateScript(command)?.toString() - } - } diff --git a/MongoView/JSONPrettyPrinter.swift b/MongoView/JSONPrettyPrinter.swift new file mode 100644 index 0000000..f9b42dc --- /dev/null +++ b/MongoView/JSONPrettyPrinter.swift @@ -0,0 +1,102 @@ +// +// JSONPrettyPrinter.swift +// MongoView +// +// Created by Shadowfacts on 8/12/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import Foundation + +class JSONPrettyPrinter { + let options: Options + private var strings = [String]() + private var currentIndent = "" + + init(options: Options) { + self.options = options + } + + func prettify(_ string: String) throws -> String { + let object = try JSONSerialization.jsonObject(with: string.data(using: .utf8)!, options: []) + + doPrettify(object) + + return strings.joined(separator: "") + } + + private func indent() { + currentIndent += "\t" + } + + private func outdent() { + currentIndent = String(currentIndent.dropLast()) + } + + private func doPrettify(_ object: Any) { + if let dict = object as? [String: Any] { + if options.contains(.convertMongoObjects) && tryPrettifyMongoObject(dict) { + return + } + indent() + strings.append("{\n") + for (index, k) in dict.keys.sorted().enumerated() { + strings.append("\(currentIndent)\"\(escape(k))\"") + strings.append(": ") + doPrettify(dict[k]!) + if index != dict.count - 1 { + strings.append(",\n") + } + } + outdent() + strings.append("\n\(currentIndent)}") + } else if let arr = object as? [Any] { + indent() + strings.append("[\n") + for (index, v) in arr.enumerated() { + strings.append(currentIndent) + doPrettify(v) + if index != arr.count - 1 { + strings.append(",\n") + } + } + outdent() + strings.append("\n\(currentIndent)]") + } else if let str = object as? String { + strings.append("\"\(escape(str))\"") + } else if let bool = object as? Bool { + strings.append(bool.description) + } else if let num = object as? NSNumber { + strings.append(num.description) + } else if object is NSNull { + strings.append("null") + } else { + fatalError("unhandled object type: \(String(describing: object))") + } + } + + private func tryPrettifyMongoObject(_ dict: [String: Any]) -> Bool { + guard dict.count == 1 else { return false } + if let value = dict["$oid"] as? String { + strings.append("ObjectId(\"\(value)\")") + return true + } + if let value = dict["$date"] as? String { + strings.append("Date(\"\(value)\")") + return true + } + return false + } + + private func escape(_ str: String) -> String { + return str.replacingOccurrences(of: "\"", with: "\\\"") + } +} + +extension JSONPrettyPrinter { + struct Options: OptionSet { + let rawValue: Int + + static let convertMongoObjects = Options(rawValue: 1 << 0) + } +} diff --git a/MongoView/View Controllers/EditDocumentViewController.swift b/MongoView/View Controllers/EditDocumentViewController.swift index 1853a87..7f9428f 100644 --- a/MongoView/View Controllers/EditDocumentViewController.swift +++ b/MongoView/View Controllers/EditDocumentViewController.swift @@ -37,7 +37,9 @@ class EditDocumentViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() - editorTextView.string = ExtendedJSON.prettify(document.extendedJSON) ?? document.extendedJSON + let printer = JSONPrettyPrinter(options: .convertMongoObjects) + editorTextView.string = (try? printer.prettify(document.extendedJSON)) ?? document.extendedJSON + editorTextView.isAutomaticQuoteSubstitutionEnabled = false } diff --git a/MongoView/Views/JavaScriptEditorView.swift b/MongoView/Views/JavaScriptEditorView.swift index 14fba4c..108cc2c 100644 --- a/MongoView/Views/JavaScriptEditorView.swift +++ b/MongoView/Views/JavaScriptEditorView.swift @@ -19,7 +19,6 @@ class JavaScriptEditorView: NSTextView { super.string } set { - isRehighlighting = true super.string = newValue rehighlight() }