.../Source/App/Services/JSFuncService.swift
Line | Count | Source (jump to first uncovered line) |
1 | | // |
2 | | // JSFuncService.swift |
3 | | // CutBox |
4 | | // |
5 | | // Created by Jason Milkins on 17/5/18. |
6 | | // Copyright © 2018-2023 ocodo. All rights reserved. |
7 | | // |
8 | | |
9 | | import JavaScriptCore |
10 | | |
11 | | class JSFuncService: NSObject { |
12 | | static var context: JSContext = JSContext() |
13 | | |
14 | | var prefs: CutBoxPreferencesService? |
15 | | |
16 | 396 | let require: @convention(block) (String) -> JSValue? = { path in |
17 | 396 | let expandedPath = NSString(string: path).expandingTildeInPath |
18 | 396 | |
19 | 396 | guard FileManager.default.fileExists(atPath: expandedPath) |
20 | 396 | else { |
21 | 1 | notifyUser( |
22 | 1 | title: "Require: filename \(expandedPath)", |
23 | 1 | info: " does not exist") |
24 | 1 | return nil |
25 | 3 | } |
26 | 3 | |
27 | 3 | guard let fileContent = getStringFromFile(expandedPath) |
28 | 3 | else { |
29 | 1 | notifyUser( |
30 | 1 | title: "Cannot read: \(expandedPath)", |
31 | 1 | info: "unknown error processing file") |
32 | 1 | return nil |
33 | 2 | } |
34 | 2 | |
35 | 2 | return JSFuncService.context.evaluateScript(fileContent) |
36 | 3 | } $s15CutBoxUnitTests13JSFuncServiceC7requireySo7JSValueCSgSSXBvpfi Line | Count | Source | 16 | 392 | let require: @convention(block) (String) -> JSValue? = { path in | 17 | 392 | let expandedPath = NSString(string: path).expandingTildeInPath | 18 | 392 | | 19 | 392 | guard FileManager.default.fileExists(atPath: expandedPath) | 20 | 392 | else { | 21 | 392 | notifyUser( | 22 | 392 | title: "Require: filename \(expandedPath)", | 23 | 392 | info: " does not exist") | 24 | 392 | return nil | 25 | 392 | } | 26 | 392 | | 27 | 392 | guard let fileContent = getStringFromFile(expandedPath) | 28 | 392 | else { | 29 | 392 | notifyUser( | 30 | 392 | title: "Cannot read: \(expandedPath)", | 31 | 392 | info: "unknown error processing file") | 32 | 392 | return nil | 33 | 392 | } | 34 | 392 | | 35 | 392 | return JSFuncService.context.evaluateScript(fileContent) | 36 | 392 | } |
$s15CutBoxUnitTests13JSFuncServiceC7requireySo7JSValueCSgSSXBvpfiAGSScfU_ Line | Count | Source | 16 | 4 | let require: @convention(block) (String) -> JSValue? = { path in | 17 | 4 | let expandedPath = NSString(string: path).expandingTildeInPath | 18 | 4 | | 19 | 4 | guard FileManager.default.fileExists(atPath: expandedPath) | 20 | 4 | else { | 21 | 1 | notifyUser( | 22 | 1 | title: "Require: filename \(expandedPath)", | 23 | 1 | info: " does not exist") | 24 | 1 | return nil | 25 | 3 | } | 26 | 3 | | 27 | 3 | guard let fileContent = getStringFromFile(expandedPath) | 28 | 3 | else { | 29 | 1 | notifyUser( | 30 | 1 | title: "Cannot read: \(expandedPath)", | 31 | 1 | info: "unknown error processing file") | 32 | 1 | return nil | 33 | 2 | } | 34 | 2 | | 35 | 2 | return JSFuncService.context.evaluateScript(fileContent) | 36 | 3 | } |
|
37 | | |
38 | 393 | let shellCommand: @convention(block) (String) -> String = { command in |
39 | 393 | let task = Process() |
40 | 393 | task.launchPath = "/bin/bash" |
41 | 393 | task.arguments = ["-c", command] |
42 | 393 | |
43 | 393 | let pipe = Pipe() |
44 | 393 | task.standardOutput = pipe |
45 | 393 | task.launch() |
46 | 393 | |
47 | 393 | let data = pipe.fileHandleForReading.readDataToEndOfFile() |
48 | 393 | return String(data: data, encoding: .utf8) ?? "" |
49 | 393 | } $s15CutBoxUnitTests13JSFuncServiceC12shellCommandyS2SXBvpfi Line | Count | Source | 38 | 392 | let shellCommand: @convention(block) (String) -> String = { command in | 39 | 392 | let task = Process() | 40 | 392 | task.launchPath = "/bin/bash" | 41 | 392 | task.arguments = ["-c", command] | 42 | 392 | | 43 | 392 | let pipe = Pipe() | 44 | 392 | task.standardOutput = pipe | 45 | 392 | task.launch() | 46 | 392 | | 47 | 392 | let data = pipe.fileHandleForReading.readDataToEndOfFile() | 48 | 392 | return String(data: data, encoding: .utf8) ?? "" | 49 | 392 | } |
$s15CutBoxUnitTests13JSFuncServiceC12shellCommandyS2SXBvpfiS2ScfU_ Line | Count | Source | 38 | 1 | let shellCommand: @convention(block) (String) -> String = { command in | 39 | 1 | let task = Process() | 40 | 1 | task.launchPath = "/bin/bash" | 41 | 1 | task.arguments = ["-c", command] | 42 | 1 | | 43 | 1 | let pipe = Pipe() | 44 | 1 | task.standardOutput = pipe | 45 | 1 | task.launch() | 46 | 1 | | 47 | 1 | let data = pipe.fileHandleForReading.readDataToEndOfFile() | 48 | 1 | return String(data: data, encoding: .utf8) ?? "" | 49 | 1 | } |
|
50 | | |
51 | | let shell: @convention(block) |
52 | 394 | (String, String?) -> [String] = { command, stdin -> [String] in |
53 | 394 | let task = Process() |
54 | 394 | |
55 | 394 | if let stdinString = stdin { |
56 | 2 | let stdinPipe = Pipe() |
57 | 2 | task.standardInput = stdinPipe |
58 | 2 | if let stdinData = stdinString.data(using: .utf8) { |
59 | 2 | stdinPipe.fileHandleForWriting.write(stdinData) |
60 | 2 | stdinPipe.fileHandleForWriting.closeFile() |
61 | 2 | } |
62 | 2 | } |
63 | 2 | |
64 | 2 | task.launchPath = "/bin/bash" |
65 | 2 | task.arguments = ["-c", command] |
66 | 2 | |
67 | 2 | let stdoutPipe = Pipe() |
68 | 2 | let stderrPipe = Pipe() |
69 | 2 | task.standardOutput = stdoutPipe |
70 | 2 | task.standardError = stderrPipe |
71 | 2 | |
72 | 2 | do { |
73 | 2 | try task.run() |
74 | 2 | } catch { |
75 | 0 | return ["", "Error running: shell(command: \(command), stdin: \(String(describing: stdin))"] |
76 | 2 | } |
77 | 2 | |
78 | 2 | let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() |
79 | 2 | let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() |
80 | 2 | |
81 | 2 | let stdout = String(data: stdoutData, encoding: .utf8) ?? "" |
82 | 2 | let stderr = String(data: stderrData, encoding: .utf8) ?? "" |
83 | 2 | |
84 | 2 | return [stdout, stderr] |
85 | 2 | } $s15CutBoxUnitTests13JSFuncServiceC5shellySaySSGSS_SSSgtXBvpfi Line | Count | Source | 52 | 392 | (String, String?) -> [String] = { command, stdin -> [String] in | 53 | 392 | let task = Process() | 54 | 392 | | 55 | 392 | if let stdinString = stdin { | 56 | 392 | let stdinPipe = Pipe() | 57 | 392 | task.standardInput = stdinPipe | 58 | 392 | if let stdinData = stdinString.data(using: .utf8) { | 59 | 392 | stdinPipe.fileHandleForWriting.write(stdinData) | 60 | 392 | stdinPipe.fileHandleForWriting.closeFile() | 61 | 392 | } | 62 | 392 | } | 63 | 392 | | 64 | 392 | task.launchPath = "/bin/bash" | 65 | 392 | task.arguments = ["-c", command] | 66 | 392 | | 67 | 392 | let stdoutPipe = Pipe() | 68 | 392 | let stderrPipe = Pipe() | 69 | 392 | task.standardOutput = stdoutPipe | 70 | 392 | task.standardError = stderrPipe | 71 | 392 | | 72 | 392 | do { | 73 | 392 | try task.run() | 74 | 392 | } catch { | 75 | 392 | return ["", "Error running: shell(command: \(command), stdin: \(String(describing: stdin))"] | 76 | 392 | } | 77 | 392 | | 78 | 392 | let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() | 79 | 392 | let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() | 80 | 392 | | 81 | 392 | let stdout = String(data: stdoutData, encoding: .utf8) ?? "" | 82 | 392 | let stderr = String(data: stderrData, encoding: .utf8) ?? "" | 83 | 392 | | 84 | 392 | return [stdout, stderr] | 85 | 392 | } |
$s15CutBoxUnitTests13JSFuncServiceC5shellySaySSGSS_SSSgtXBvpfiAESS_AFtcfU_ Line | Count | Source | 52 | 2 | (String, String?) -> [String] = { command, stdin -> [String] in | 53 | 2 | let task = Process() | 54 | 2 | | 55 | 2 | if let stdinString = stdin { | 56 | 2 | let stdinPipe = Pipe() | 57 | 2 | task.standardInput = stdinPipe | 58 | 2 | if let stdinData = stdinString.data(using: .utf8) { | 59 | 2 | stdinPipe.fileHandleForWriting.write(stdinData) | 60 | 2 | stdinPipe.fileHandleForWriting.closeFile() | 61 | 2 | } | 62 | 2 | } | 63 | 2 | | 64 | 2 | task.launchPath = "/bin/bash" | 65 | 2 | task.arguments = ["-c", command] | 66 | 2 | | 67 | 2 | let stdoutPipe = Pipe() | 68 | 2 | let stderrPipe = Pipe() | 69 | 2 | task.standardOutput = stdoutPipe | 70 | 2 | task.standardError = stderrPipe | 71 | 2 | | 72 | 2 | do { | 73 | 2 | try task.run() | 74 | 2 | } catch { | 75 | 0 | return ["", "Error running: shell(command: \(command), stdin: \(String(describing: stdin))"] | 76 | 2 | } | 77 | 2 | | 78 | 2 | let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() | 79 | 2 | let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() | 80 | 2 | | 81 | 2 | let stdout = String(data: stdoutData, encoding: .utf8) ?? "" | 82 | 2 | let stderr = String(data: stderrData, encoding: .utf8) ?? "" | 83 | 2 | | 84 | 2 | return [stdout, stderr] | 85 | 2 | } |
|
86 | | |
87 | | var filterText: String = "" |
88 | | |
89 | 66 | var funcs: [(String, Int)] { |
90 | 66 | guard let funcsDict: [[String: Any]] = js["cutboxFunctions"] |
91 | 66 | .toArray() as? [[String: Any]] else { |
92 | 40 | return [] |
93 | 40 | } |
94 | 26 | |
95 | 26 | var idx = 0 |
96 | 27 | return funcsDict.map { (dict: [String: Any]) -> (String, Int)? in |
97 | 27 | if let name = dict["name"] as? String { |
98 | 13 | let index = (name, idx) |
99 | 13 | idx += 1 |
100 | 13 | return index |
101 | 14 | } else { |
102 | 14 | return nil |
103 | 14 | } |
104 | 0 | } |
105 | 27 | .compactMap { $0 } |
106 | 66 | |
107 | 66 | } |
108 | | |
109 | 59 | var funcList: [String] { |
110 | 59 | let names: [String] = funcs.map { (t: (String, Int)) -> String in t.0 } |
111 | 59 | if filterText.isEmpty { |
112 | 58 | return names |
113 | 58 | } |
114 | 1 | return names.substringSearchFiltered(search: filterText) |
115 | 59 | } |
116 | | |
117 | 4 | func selected(name: String) -> (String, Int)? { |
118 | 4 | guard let found = funcs.first(where: { $0.0 == name }) |
119 | 4 | else { return nil } |
120 | 3 | return found |
121 | 4 | } |
122 | | |
123 | 392 | var js: JSContext = JSFuncService.context |
124 | | |
125 | 77 | var count: Int { |
126 | 77 | return self.funcList.count |
127 | 77 | } |
128 | | |
129 | 5 | var isEmpty: Bool { |
130 | 5 | return self.funcList.isEmpty |
131 | 5 | } |
132 | | |
133 | | var helpers = """ |
134 | | help = ` |
135 | | CutBox JS Help: |
136 | | cutboxFunctions - Array of loaded functions |
137 | | |
138 | | ${ls()}` |
139 | | |
140 | | var ls = () => cutboxFunctions.map(e => e.name).join(`\n`) |
141 | | var list = () => JSON.stringify(cutboxFunctions, null, 2) |
142 | | """ |
143 | | |
144 | | var noCutboxJSHelp = """ |
145 | | var help = `CutBox JS Help:\n |
146 | | ~/.cutbox.js does not exist\n |
147 | | Add and reload to paste through JS. |
148 | | See the wiki for more advanced info: |
149 | | https://github.com/cutbox/CutBox/wiki` |
150 | | """ |
151 | | |
152 | 27 | func setup() { |
153 | 27 | self.js["require"] = self.require |
154 | 27 | self.js["shellCommand"] = self.shellCommand |
155 | 27 | self.js["shell"] = self.shell |
156 | 27 | } |
157 | | |
158 | 5 | func reload() { |
159 | 5 | setup() |
160 | 5 | repl(noCutboxJSHelp) |
161 | 5 | |
162 | 5 | guard let cutBoxJSLocation = prefs?.cutBoxJSLocation, |
163 | 5 | let cutboxJS = getStringFromFile(cutBoxJSLocation) else { |
164 | 2 | return |
165 | 3 | } |
166 | 3 | |
167 | 3 | repl(helpers) |
168 | 3 | repl(cutboxJS) |
169 | 3 | |
170 | 3 | if self.isEmpty { |
171 | 2 | notifyUser(title: "Problem in cutbox js", |
172 | 2 | info: "Error: no function objects in cutboxFunctions") |
173 | 3 | } else { |
174 | 1 | notifyUser(title: "Javascript loaded", |
175 | 1 | info: "cutbox js loaded \(self.count) function(s)") |
176 | 3 | } |
177 | 3 | } |
178 | | |
179 | | @discardableResult |
180 | 45 | func repl(_ line: String) -> String { |
181 | 45 | return js.evaluateScript(line).toString() |
182 | 45 | } |
183 | | |
184 | 1 | func replValue(_ line: String) -> JSValue? { |
185 | 1 | return js.evaluateScript(line) |
186 | 1 | } |
187 | | |
188 | 3 | func process(_ name: String, items: [String]) -> String { |
189 | 3 | guard let index = selected(name: name)?.1 |
190 | 3 | else { return "" } |
191 | 2 | return self.process(index, items: items) |
192 | 3 | } |
193 | | |
194 | 2 | func process(_ fnIndex: Int, items: [String]) -> String { |
195 | 2 | return js.evaluateScript("cutboxFunctions[\(fnIndex)].fn") |
196 | 2 | .call(withArguments: [items]).toString()! |
197 | 2 | } |
198 | | } |
199 | | |
200 | 9 | func getStringFromFile(_ expandedFilename: String) -> String? { |
201 | 9 | guard let fileContent = try? String(contentsOfFile: expandedFilename) |
202 | 9 | else { return nil } |
203 | 6 | return fileContent |
204 | 9 | } |